看過之前的蛻變系列文章,相信你對mybatis有了初步的認識。但是這些還不夠,我們今天進一步來了解下mybatis的一些用法。
猿蛻變同樣是一個原創系列文章,幫助你從一個普通的小白,開始掌握一些行業內通用的框架技術知識以及鍛煉你對系統設計能力的提升,完成屬于你的蛻變,更多精彩內容,還是私信我吧,黑機構太多,老是舉報我,大號都被封號了(由此可見對這些黑機構的傷害有多深)。






我們第一個程序存在很多問題,每一次操作,都需要讀取配置文件、初始化mybati框架。這樣搞出來的程序上就一個字——渣!這讓我想起了多年以前,某個小伙伴告訴我spring的正確使用一樣,每次方法都讓spring框架重新初始化了一次。哈哈,知道你也在看的,又是一波回憶殺。
在第一個mybatis程序中,獲取SqlSession對象的方式也比較復雜,獲取SqlSession對象的操作比較復雜,由于SqlSessionFactory本身就是用來管理SqlSession對象的,應用中一般情況下有一個就足夠了!我們做一個小優化——使用單利模式來封裝下,讓一個SqlSessionFactory對象去管理SqlSession對象。

1.創建一個MyBatisUtil工具類:
package com.pz.route.util;import java.io.IOException;
import java.io.InputStream;import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;public class MyBatisUtil {private static SqlSessionFactory sqlSessionFactory;static{//讀取主配置文件InputStream input = null;if (sqlSessionFactory == null) {try {input = Resources.getResourceAsStream("mybatis.xml");} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}synchronized (MyBatisUtil.class) {if (sqlSessionFactory == null){sqlSessionFactory = new SqlSessionFactoryBuilder().build(input);}}}}publicstatic SqlSession getSqlSession() {returnsqlSessionFactory.openSession();}}
上面的例子使用了靜態代碼塊的方式創建了一個SqlSessionFactory對象,將SqlSession交給SqlSessionFactory進行管理。
2.修改TravelRouteDaoImpl類:
package com.pz.route.dao.impl;import org.apache.ibatis.session.SqlSession;import com.pz.route.dao.TravelRouteDao;
import com.pz.route.domain.TravelRoute;
import com.pz.route.util.MyBatisUtil;public class TravelRouteDaoImpl implements TravelRouteDao {private SqlSession sqlSession;@Overridepublic void add(TravelRoute travelRoute) {try {sqlSession = MyBatisUtil.getSqlSession();//新增數據操作sqlSession.insert("com.pz.route.dao.TravelRouteDao.add", travelRoute);//提交SqlSessionsqlSession.commit();//無需再做關閉,SqlSessionFactory會自動關閉sqlSession} catch (Exception e) {e.printStackTrace();}}}
SqlSession繼承了AutoCloseable接口,SqlSessionFactory會自動關閉SqlSession。

數據庫的一些信息最好不要寫死在xml里,大家基本上都是通過配置的方式讀取數據庫相關信息。在resources目錄下創建db.properties配置文件,里面填寫下面內容:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/route?characterEncoding=utf8
jdbc.username=root
jdbc.password=123456
修改mybatis.xml文件,使用將數據庫連接信息修改為使用配置文件的方式:
<?xml version="1.0"encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!-- 引入數據庫配置信息 --><properties resource="db.properties"/><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver"value="${jdbc.driverClassName}"/><property name="url"value="${jdbc.url}"/><property name="username"value="${jdbc.username}"/><property name="password"value="${jdbc.password}"/></dataSource></environment></environments><mappers><!--注冊映射文件--><mapper resource="mapper/TravelRouteMapper.xml"/></mappers>
</configuration>

我們新建了Mybatis.xml作為使用Mytatis的主配置文件,那么這個主配置文件應該干些什么事情呢?
1.配置外部配置文件,方便程序使用(比如配置db.properties)
2.設置全局Domain類JavaBean的別名(每次使用類的名稱空間寫起來太麻煩了)
3.配置運行環境
4.配置mapper文件(有哪些數據操作使用了mybatis需要要告訴框架)
5. 設置全局的數據庫操作配置,比如事務超時時間等等信息
我們看看mybatis.xml中的內容:
<properties resource="db.properties"/
這樣配置以后,mybatis就可以從db.properties中獲取數據庫信息了。
我們看看這個配置我們在mapper文件中的寫法:
<insert id="add"parameterType="com.pz.route.domain.TravelRoute">
在mapper中的parameterType需要使用類的名稱空間:包名+類名的方式。每次這樣寫比較麻煩,也比較容易寫錯。MyBatis提供了<typeAliases>標簽用以定義別名。
<typeAliases><typeAlias type="com.pz.route.domain.TravelRoute"alias="TravelRoute"/></typeAliases>
這樣子,在mapper中就可以直接使用TravelRoute來代替之前的 com.pz.route.domain.TravelRoute。
一個項目上線,需要經歷多個階段,一般來講一個項目需要經過開發,測試,上線的生命周期。那么不可避免的,一個項目會有多個運行環境。
比如在開發階段,應用往往運行在本地,測試階段,會運行在測試服務器,到了應用上線之后,應用自然運行在生產機器上。在不同的環境下,數據庫也會有多個,比如開發環境在A庫,測試時在B庫,項目上線以后在生產庫C,那么為了支持不同的環境,mybatis提供了<environments>標簽來支持多個環境的問題。
<environments default="development"><environment id="development"><transactionManager type="JDBC" /><dataSource type="POOLED"><property name="driver"value="${jdbc.driverClassName}" /><property name="url"value="${jdbc.url}" /><property name="username"value="${jdbc.username}" /><property name="password"value="${jdbc.password}" /></dataSource></environment><!--測試環境 --><environment id="test"><transactionManager type="JDBC" /><dataSource type="POOLED"><property name="driver"value="${jdbc.test.driver}" /><property name="url"value="${jdbc.test.url}" /><property name="username"value="${jdbc.test.user}" /><property name="password"value="${jdbc.test.password}" /></dataSource></environment></environments>
大家可以看到再environments標簽中有兩個environment子標簽,配置了兩個數據庫信息environments標簽中的default屬性,用于選擇到底使用的是哪個環境。
transactionManager標簽用于設置事務管理器用于MyBatis的事務管理。MyBatis 支持兩種事務管理器類型:
JDBC
需要再程序中使用通過SqlSession對象的commit()方法提交,通過rollback()方法回滾,默認情況下是需要手動提交的。
MANAGED
由Mybatis來管理事務的整個事務的生命周期,默認情況下,每次操作都會關閉數據庫連接。
dataSource標簽主要用于配置應用的數據源連接方式和數據庫連接信息,使用type屬性來設置數據源類型:
UNPOOLED
表示不使用數據庫連接池,每次數據庫操作Mybatis都需要創建一個Connection對象。
POOLED
使用Mybatis提供的數據庫連接池。
JNDI
使用JNDI數據源(JNDI的方式一般是配置在應用服務器中)

Mapper標簽的作用就是通知Mybatis使用哪些mapper文件,一共有四種寫法:
第一種:使用mapper文件的相對路徑,這個路徑是相對于classpath而言的。
<!--注冊映射文件 --><mapper resource="mapper/TravelRouteMapper.xml" />
第二種:使用url
<!--注冊映射文件 --><mapper url="D://xxx/xxxMapper.xml" />
第三種:使用類的名稱空間,使用這種方式,需要滿足以下幾個規約:
1.Mapper文件名要與 Dao 接口名相同
2.Mapper文件要與接口在同一包中
3.Mapper文件中的 namespace 屬性值為 Dao 接口的類的名稱空間
<!--注冊映射文件 --><mapper class="com.pz.route.dao.TravelRouteDao" />
第四種:使用mybatis提供的動態代理實現(無需自己實現xxxDaoImpl)
使用這種方式,需要滿足以下幾個規約:
(1)dao 使用 mapper 的動態代理實現(后面再將)
(2)Mapper文件名與 Dao 接口名相同
(3)Mapper文件與接口放在同一包中
(4)Mapper文件中的 namespace 屬性值為 Dao接口的類的名稱空間
<!--注冊映射文件 -->
<mapper package="com.pz.route.dao" />

我們編寫的第一個MyBatis程序,實現了一個向route數據庫travel_route表中新增一條數據的功能,有一些比較值得注意的地方大家一起來看下:
<insert id="add"parameterType="com.pz.route.domain.TravelRoute">INSERTINTO travel_route(travel_route_name,travel_route_price,travel_route_introduce,travel_route_flag,travel_route_date,isThemeTour,travel_route_count,travel_route_cid,travel_route_image,travel_route_seller_id)values(#{travelRouteName},#{travelRoutePrice},#{travelRouteIntroduce},#{travelRouteFlag},#{travelRouteDate},#{isThemeTour},#{travelRouteCount},#{travelRouteCid},#{travelRouteImage},#{travelRouteSellerId})</insert>
id:需要執行的SQL語句的唯一標識,實際上它mybatix被識別的方式是namespace+id,一個id可被用代表一段需要執行的sql語句,在一個mapper文件中對id值的要求是不能重復出現的。
parameterType:sql語句中參數的類型,實際上MyBatis利用反射機制感知dao接口的參數類型,不寫這個配置也是可以的
#{ }:這個符號表示參數,支持el表達式,我們通過JavaBean的方式做參數傳遞,花括號里的值為JavaBean的屬性,如果屬性也是對象,支持對象名.方法名的形式。
Mybatis事務提交,我們看TravelRouteDaoImpl:
try {sqlSession = MyBatisUtil.getSqlSession();//新增數據操作sqlSession.insert("com.pz.route.dao.TravelRouteDao.add", travelRoute);//提交SqlSessionsqlSession.commit();//無需再做關閉,SqlSessionFactory會自動關閉sqlSession} catch (Exception e) {e.printStackTrace();}
默認情況下Mybatis的事務是不會自動提交的,如果想自動提交事務,可以在MyBatisUtil中修改openSession的方法,傳入參數true。如果不傳入任何參數或者傳入false,mybatis無法自動提交事務。
public static SqlSession getSqlSession() {return sqlSessionFactory.openSession(true);}
如果不提交事務,數據是不會持久化到數據庫的。
travel_route表,我們設置的主鍵是自增長類型,所以在編寫SQL的時候,我們不用寫入主鍵travel_route_id,如果我們想在插入數據后獲取主鍵我們可以使用下面的方式:
<selectKey resultType="Long"keyProperty="travelRouteId" order="AFTER">SELECT @@identity</selectKey>
這樣子在mybatis完成insert操作之后后,會將當前寫入數據的ID查詢出來,重寫Travle的toString方法,再編寫一段程序看下效果:
@Testpublic void testAddTravelRoute(){TravelRouteDao travelRouteDao = new TravelRouteDaoImpl();TravelRoute travelRoute = new TravelRoute();travelRoute.setTravelRouteName("新增線路2");travelRoute.setTravelRoutePrice(999d);travelRoute.setTravelRouteDate("2019-10-26");travelRoute.setTravelRouteFlag(1);travelRoute.setIsThemeTour("1");travelRoute.setTravelRouteCount(0);travelRoute.setTravelRouteCid(1);travelRoute.setTravelRouteIntroduce("雙導游服務,免收服務小費,周全照顧貼心服務隨心出游!品嘗越南特色國寶美食,升級一餐越式炸雞火鍋宴!");travelRoute.setTravelRouteImage("img/product/small/m3db4d2277b5df3d98597f79082ef92d6d.jpg");travelRoute.setTravelRouteSellerId(1L);System.out.println("before insert===");System.out.println(travelRoute);travelRouteDao.add(travelRoute);System.out.println("after insert===");System.out.println(travelRoute);}
我們再在TravelRouteDaoImpl中commit打印增加兩行打印代碼
@Overridepublic void add(TravelRoute travelRoute) {try {sqlSession = MyBatisUtil.getSqlSession();//新增數據操作sqlSession.insert("com.pz.route.dao.TravelRouteDao.add", travelRoute);System.out.println("before commit===");System.out.println(travelRoute);//提交SqlSessionsqlSession.commit();//無需再做關閉,SqlSessionFactory會自動關閉sqlSession} catch (Exception e) {e.printStackTrace();}}
運行程序后,我們可以清晰地看到,在commit之前,id已經有值了,實際上,當insert語句執行后,commit之前,主鍵id已經返回了數據,這個事務不管是commit或者是rollback,travel_route的主鍵已經使用掉一個了,再做insert操作mysql只會分配一個新的主鍵。由此我們得出一個結論:數據庫主鍵的分配和是否提交無關,只要執行了insert操作,mysql就會分配一個主鍵。