MyBatis使用詳解上篇我們手動(dòng)開(kāi)發(fā)了一個(gè)MyBatis項(xiàng)目,但是我們僅僅是編寫(xiě)了代碼,對(duì)于整個(gè)項(xiàng)目是如何運(yùn)行以及每個(gè)代碼的意義都沒(méi)有仔細(xì)的分析和說(shuō)明,那么接下來(lái)我們就開(kāi)始分析每個(gè)代碼的意義以及如何編寫(xiě)這個(gè)代碼 配置MyBatis全局配置文件要使用Mybatis來(lái)操作數(shù)據(jù)庫(kù),那么當(dāng)然就需要配置數(shù)據(jù)庫(kù)相關(guān)信息,這件需要在mybatis全局配置文件中進(jìn)行。即全局配置的xml文件,其對(duì)整個(gè)MyBatis進(jìn)行事務(wù)的支持、數(shù)據(jù)庫(kù)的配置等信息的配置。我們一般放在main/resource文件中,如下所示 <configuration> <!-- 環(huán)境配置,可以配置多個(gè)環(huán)境 --> <environments default="chat01"> <!-- environment用來(lái)對(duì)某個(gè)環(huán)境進(jìn)行配置 id:環(huán)境標(biāo)識(shí),唯一 --> <environment id="chat01"> <!-- 事務(wù)管理器工廠配置 --> <transactionManager type="org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory"/> <!-- 數(shù)據(jù)源工廠配置,使用工廠來(lái)創(chuàng)建數(shù)據(jù)源 --> <dataSource type="org.apache.ibatis.datasource.pooled.PooledDataSourceFactory"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatisdemo?characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> </configuration> configuration元素這個(gè)是mybatis全局配置文件的根元素,每個(gè)配置文件只有一個(gè) environments元素用來(lái)配置mybatis的環(huán)境信息,用來(lái)配置多個(gè)環(huán)境的,具體的一個(gè)環(huán)境使用environment元素進(jìn)行配置,environment元素有個(gè)id用來(lái)標(biāo)識(shí)某個(gè)具體的環(huán)境。 environments元素有個(gè)default屬性,用來(lái)指定默認(rèn)使用哪個(gè)環(huán)境,如上面默認(rèn)使用的是chat01。 environment元素用來(lái)配置具體的環(huán)境信息,這個(gè)元素下面有兩個(gè)子元素:transactionManager和dataSource
用來(lái)配置事務(wù)工廠的,有個(gè)type屬性,type的值必須是org.apache.ibatis.transaction.TransactionFactory接口的實(shí)現(xiàn)類(lèi),用來(lái)創(chuàng)建事務(wù)管理器對(duì)象的,TransactionFactory接口默認(rèn)有2個(gè)實(shí)現(xiàn): org.apache.ibatis.transaction.managed.ManagedTransactionFactory org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory 一般情況下我們使用org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory這個(gè),mybatis和其他框架集成,比如和spring集成,事務(wù)交由spring去控制。
這個(gè)用來(lái)配置數(shù)據(jù)源的,type屬性的值必須為接口org.apache.ibatis.datasource.DataSourceFactory的實(shí)現(xiàn)類(lèi),DataSourceFactory也是一個(gè)工廠,用來(lái)創(chuàng)建數(shù)據(jù)源javax.sql.DataSource對(duì)象的,mybatis中這個(gè)接口默認(rèn)有3個(gè)實(shí)現(xiàn)類(lèi): org.apache.ibatis.datasource.jndi.JndiDataSourceFactory org.apache.ibatis.datasource.pooled.PooledDataSourceFactory org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 我們使用第2個(gè)org.apache.ibatis.datasource.pooled.PooledDataSourceFactory,這個(gè)用來(lái)創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)連接池類(lèi)型的數(shù)據(jù)源,可以實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接共用,減少連接重復(fù)創(chuàng)建銷(xiāo)毀的時(shí)間。 <property name="屬性名稱" value="值"/> 創(chuàng)建Mapper xml文件在mybatis中一般我們將一個(gè)表的所有sql操作寫(xiě)在一個(gè)mapper xml中,一般命名為XXXMapper.xml格式。 內(nèi)容如下: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-////DTD Mapper 3.0//EN" "http:///dtd/mybatis-3-mapper.dtd"> <mapper namespace="zhonghu.mybatis.chat01.UserMapper"> </mapper> mapper xml根元素為mapper,這個(gè)元素有個(gè)namespace屬性,系統(tǒng)中會(huì)有很多表,每個(gè)表對(duì)應(yīng)一個(gè)Mapper xml,為了防止mapper文件重復(fù),我們需要給每個(gè)mapper xml文件需要指定一個(gè)namespace,通過(guò)這個(gè)可以區(qū)分每個(gè)mapper xml文件,上面我們指定為zhonghu.mybatis.chat01.UserMapper。 一會(huì)對(duì)user表的所有操作相關(guān)的sql,我們都會(huì)寫(xiě)在上面這個(gè)xml中。 mybatis全局配置文件中引入Mapper xml文件user.xml我們寫(xiě)好了,如何讓mybatis知道這個(gè)文件呢,此時(shí)我們需要在mybatis-config.xml全局配置文件中引入U(xiǎn)serMapper.xml,在mybatis-config.xml加入下面配置: <mappers> <mapper resource="mapper/user.xml"/> </mappers> mappers元素下面有多個(gè)mapper元素,通過(guò)mapper元素的resource屬性可以引入Mapper xml文件,resource是相對(duì)于classes的路徑。 上面說(shuō)的都是一些配置文件,配置文件都o(jì)k了,下面我們就需要將mybatis跑起來(lái)了,此時(shí)需要使用到mybatis中的一些java對(duì)象了。 構(gòu)建SqlSessionFactory對(duì)象//指定mybatis全局配置文件 String resource = "mybatis-config.xml"; //讀取全局配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); //構(gòu)建SqlSessionFactory對(duì)象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSessionFactory是一個(gè)接口,是一個(gè)重量級(jí)的對(duì)象,SqlSessionFactoryBuilder通過(guò)讀取全局配置文件來(lái)創(chuàng)建一個(gè)SqlSessionFactory,創(chuàng)建這個(gè)對(duì)象是比較耗時(shí)的,主要耗時(shí)在對(duì)mybatis全局配置文件的解析上面,全局配置文件中包含很多內(nèi)容,SqlSessionFactoryBuilder通過(guò)解析這些內(nèi)容,創(chuàng)建了一個(gè)復(fù)雜的SqlSessionFactory對(duì)象,這個(gè)對(duì)象的生命周期一般和應(yīng)用的生命周期是一樣的,隨著應(yīng)用的啟動(dòng)而創(chuàng)建,隨著應(yīng)用的停止而結(jié)束,所以一般是一個(gè)全局對(duì)象,一般情況下一個(gè)db對(duì)應(yīng)一個(gè)SqlSessionFactory對(duì)象。 構(gòu)建SqlSession對(duì)象SqlSession相當(dāng)于jdbc中的Connection對(duì)象,相當(dāng)于數(shù)據(jù)庫(kù)的一個(gè)連接,可以用SqlSession來(lái)對(duì)db進(jìn)行操作:如執(zhí)行sql、提交事務(wù)、關(guān)閉連接等等,需要通過(guò)SqlSessionFactory來(lái)創(chuàng)建SqlSession對(duì)象,SqlSessionFactory中常用的有2個(gè)方法來(lái)創(chuàng)建SqlSession對(duì)象,如下: //創(chuàng)建一個(gè)SqlSession,默認(rèn)不會(huì)自動(dòng)提交事務(wù) SqlSession openSession(); //創(chuàng)建一個(gè)SqlSession,autoCommit:指定是否自動(dòng)提交事務(wù) SqlSession openSession(boolean autoCommit); SqlSession接口中很多方法,直接用來(lái)操作db,方法清單如下,大家眼熟一下: <T> T selectOne(String statement); <T> T selectOne(String statement, Object parameter); <E> List<E> selectList(String statement); <E> List<E> selectList(String statement, Object parameter); <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds); <K, V> Map<K, V> selectMap(String statement, String mapKey); <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey); <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds); <T> Cursor<T> selectCursor(String statement); <T> Cursor<T> selectCursor(String statement, Object parameter); <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds); void select(String statement, Object parameter, ResultHandler handler); void select(String statement, ResultHandler handler); void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler); int insert(String statement); int insert(String statement, Object parameter); int update(String statement); int update(String statement, Object parameter); int delete(String statement); int delete(String statement, Object parameter); void commit(); void commit(boolean force); void rollback(); void rollback(boolean force); List<BatchResult> flushStatements(); void close(); void clearCache(); Configuration getConfiguration(); <T> T getMapper(Class<T> type); Connection getConnection(); 上面以select開(kāi)頭的可以對(duì)db進(jìn)行查詢操作,insert相關(guān)的可以對(duì)db進(jìn)行插入操作,update相關(guān)的可以對(duì)db進(jìn)行更新操作。 引入lombok支持(非必須)Lombok能以簡(jiǎn)單的注解形式來(lái)簡(jiǎn)化java代碼,提高開(kāi)發(fā)人員的開(kāi)發(fā)效率。例如開(kāi)發(fā)中經(jīng)常需要寫(xiě)的javabean,都需要花時(shí)間去添加相應(yīng)的getter/setter,也許還要去寫(xiě)構(gòu)造器、equals等方法,而且需要維護(hù),當(dāng)屬性多時(shí)會(huì)出現(xiàn)大量的getter/setter方法,這些顯得很冗長(zhǎng)也沒(méi)有太多技術(shù)含量,一旦修改屬性,就容易出現(xiàn)忘記修改對(duì)應(yīng)方法的失誤。 Lombok能通過(guò)注解的方式,在編譯時(shí)自動(dòng)為屬性生成構(gòu)造器、getter/setter、equals、hashcode、toString方法。出現(xiàn)的神奇就是在源碼中沒(méi)有g(shù)etter和setter方法,但是在編譯生成的字節(jié)碼文件中有g(shù)etter和setter方法。這樣就省去了手動(dòng)重建這些代碼的麻煩,使代碼看起來(lái)更簡(jiǎn)潔些。 lombok的使用步驟
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <scope>provided</scope> </dependency>
引入logback(非必須)為了方便查看mybatis運(yùn)行過(guò)程中產(chǎn)生的日志,比如:執(zhí)行的sql、sql的參數(shù)、sql的執(zhí)行結(jié)果等等調(diào)試信息,我們需要引入日志框架的支持,logback是一個(gè)很好的日志框架,此處我們就使用這個(gè) mybatis中集成logback步驟
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <logger name="zhonghu" level="debug" additivity="false"> <appender-ref ref="STDOUT" /> </logger> </configuration>
寫(xiě)一個(gè)測(cè)試用例上面說(shuō)了這么多,下面我們就寫(xiě)一個(gè)測(cè)試類(lèi)來(lái)演示一下 內(nèi)容如下: package zhonghu.mybatis.chat01; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; @Slf4j public class UserMapperTest { private SqlSessionFactory sqlSessionFactory; @Before public void before() throws IOException { //指定mybatis全局配置文件 String resource = "mybatis-config.xml"; //讀取全局配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); //構(gòu)建SqlSessionFactory對(duì)象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); this.sqlSessionFactory = sqlSessionFactory; } @Test public void sqlSession() { SqlSession sqlSession = this.sqlSessionFactory.openSession(); log.info("{}", sqlSession); } } 上面代碼中有個(gè)@Slf4j注解,這個(gè)是lombok提供的,可以在這個(gè)類(lèi)中生成下面代碼: private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserTest.class); 運(yùn)行一下上面的用例:sqlSession方法,輸出如下: 37:52.473 [main] INFO z.mybatis.chat01.UserMapperTest - org.apache.ibatis.session.defaults.DefaultSqlSession@2d127a61 至此我們就搭建了一個(gè)最小化的Mybatis項(xiàng)目,后面就需要我們根據(jù)需要編寫(xiě)我們的sql文件 使用SqlSesion執(zhí)行sql操作SqlSession常見(jiàn)的用法SqlSession相當(dāng)于一個(gè)連接,可以使用這個(gè)對(duì)象對(duì)db執(zhí)行增刪改查操作,操作完畢之后需要關(guān)閉,使用步驟:
如下所示 //獲取SqlSession SqlSession sqlSession = this.sqlSessionFactory.openSession(); try { //執(zhí)行業(yè)務(wù)操作,如:增刪改查 } finally { //關(guān)閉SqlSession sqlSession.close(); } 上面我們將SqlSession的關(guān)閉放在finally塊中,確保close()一定會(huì)執(zhí)行。 新增操作需求:傳入U(xiǎn)serModel對(duì)象,然后將這個(gè)對(duì)象的數(shù)據(jù)插入到user表中。 創(chuàng)建一個(gè)UserModelUserModel類(lèi),代碼如下: package zhonghu.mybatis.chat01; import lombok.*; @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder @ToString public class UserModel { private Long id; private String name; private Integer age; private Double salary; } 這個(gè)類(lèi)的字段和user表對(duì)應(yīng)。 user.xml中定義插入操作我們說(shuō)過(guò)了,對(duì)user表的所有sql操作,我們都放在user.xml中,我們?cè)趗ser.xml中加入下面配置,使用insert元素定義插入操作: <!-- insert用來(lái)定義一個(gè)插入操作 id:操作的具體標(biāo)識(shí) parameterType:指定插入操作接受的參數(shù)類(lèi)型 --> <insert id="insertUser" parameterType="zhonghu.mybatis.chat01.UserModel"> <![CDATA[ INSERT INTO user (id,name,age,salary) VALUES (#{id},#{name},#{age},#{salary}) ]]> </insert>
需要插入的值從UserModel對(duì)象中獲取,取UserModel對(duì)象的的字段,使用#{字段}這種格式可以獲取到UserModel中字段的值。 調(diào)用SqlSession.insert方法執(zhí)行插入操作user插入的sql我們已經(jīng)在UserMapper中寫(xiě)好,此時(shí)我們?cè)趺凑{(diào)用呢? 需要調(diào)用SqlSession.insert方法: int insert(String statement, Object parameter) 這個(gè)方法有2個(gè)參數(shù):
zhonghu.mybatis.chat01.UserMapper.insertUser
返回值為插入的行數(shù)。 UserTest類(lèi)中新增一個(gè)測(cè)試用例: @Test public void insertUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(false);) { //創(chuàng)建UserModel對(duì)象 UserModel userModel = UserModel.builder().id(69L).name("Java冢狐").age(30).salary(50000D).build(); //執(zhí)行插入操作 int result = sqlSession.insert("zhonghu.mybatis.chat01.UserMapper.insertUser", userModel); log.info("插入影響行數(shù):{}", result); //提交事務(wù) sqlSession.commit(); } } 運(yùn)行輸出如下: 05:15.831 [main] DEBUG z.m.chat01.UserMapper.insertUser - ==> Preparing: INSERT INTO user (id,name,age,salary) VALUES (?,?,?,?) 05:15.853 [main] DEBUG z.m.chat01.UserMapper.insertUser - ==> Parameters: 69(Long), Java冢狐(String), 30(Integer), 50000.0(Double) 05:15.951 [main] DEBUG z.m.chat01.UserMapper.insertUser - <== Updates: 1 05:15.952 [main] INFO z.mybatis.chat01.UserMapperTest - 插入影響行數(shù):1
上面代碼中創(chuàng)建SqlSession,我們使用的是sqlSessionFactory.openSession()創(chuàng)建的,這個(gè)方法創(chuàng)建的SqlSession,內(nèi)部事務(wù)是非自動(dòng)提交的方式,所以需要我們手動(dòng)提交:
sqlSession.commit(); 如果想自動(dòng)提交事務(wù),在上面在創(chuàng)建SqlSession的時(shí)候改為sqlSessionFactory.openSession(true),指定事務(wù)為自動(dòng)提交模式,所以最后我們不需要手動(dòng)提交事務(wù)了。 更新操作需求:傳入U(xiǎn)serModel對(duì)象,然后通過(guò)id更新數(shù)據(jù)。 UserMapper.xml中定義Update操作使用update定義更新操作: <!-- update用來(lái)定義一個(gè)更新操作 id:操作的具體標(biāo)識(shí) parameterType:指定操作接受的參數(shù)類(lèi)型 --> <update id="updateUser" parameterType="zhonghu.mybatis.chat01.UserModel"> <![CDATA[ UPDATE user SET name = #{name},age = #{age},salary = #{salary} WHERE id = #{id} ]]> </update> 寫(xiě)法和insert操作的寫(xiě)法類(lèi)似,指定id標(biāo)識(shí)、parameterType指定操作的參數(shù)類(lèi)型,元素體中是具體的sql語(yǔ)句。 調(diào)用SqlSession.update方法執(zhí)行更新操作需要調(diào)用SqlSession.update方法: int update(String statement, Object parameter) 這個(gè)方法有2個(gè)參數(shù):
zhonghu.mybatis.chat01.UserMapper.updateUser
返回值為update影響行數(shù)。 UserTest類(lèi)中新增一個(gè)測(cè)試用例: @Test public void updateUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { //創(chuàng)建UserModel對(duì)象 UserModel userModel = UserModel.builder().id(1L).name("Java冢狐,你好").age(18).salary(5000D).build(); //執(zhí)行更新操作 int result = sqlSession.update("zhonghu.mybatis.chat01.UserMapper.updateUser", userModel); log.info("影響行數(shù):{}", result); } } 運(yùn)行輸出: 12:17.143 [main] DEBUG z.m.chat01.UserMapper.updateUser - ==> Preparing: UPDATE user SET name = ?,age = ?,salary = ? WHERE id = ? 12:17.163 [main] DEBUG z.m.chat01.UserMapper.updateUser - ==> Parameters: Java冢狐,你好(String), 18(Integer), 5000.0(Double), 1(Long) 12:17.258 [main] DEBUG z.m.chat01.UserMapper.updateUser - <== Updates: 1 12:17.258 [main] INFO z.mybatis.chat01.UserMapperTest - 影響行數(shù):1 刪除操作需求:根據(jù)用戶的id刪除對(duì)應(yīng)的用戶記錄 UserMapper.xml中定義Delete操作使用update元素定義刪除操作: <!-- update用來(lái)定義一個(gè)刪除操作 id:操作的具體標(biāo)識(shí) parameterType:指定操作接受的參數(shù)類(lèi)型 --> <update id="deleteUser" parameterType="java.lang.Long"> <![CDATA[ DELETE FROM user WHERE id = #{id} ]]> </update> 寫(xiě)法和update操作的寫(xiě)法類(lèi)似,指定id標(biāo)識(shí)、parameterType指定操作的參數(shù)類(lèi)型,用戶id為L(zhǎng)ong類(lèi)型的,元素體中是具體的delete語(yǔ)句。 調(diào)用SqlSession.update方法執(zhí)行更新操作需要調(diào)用SqlSession.delete方法: int delete(String statement, Object parameter) 這個(gè)方法有2個(gè)參數(shù):
com.javacode2018.chat02.UserMapper.
返回值為delete影響行數(shù)。 UserTest類(lèi)中新增一個(gè)測(cè)試用例: @Test public void deleteUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { //定義需要?jiǎng)h除的用戶id Long userId = 1L; //執(zhí)行刪除操作 int result = sqlSession.delete("zhonghu.mybatis.chat01.UserMapper.deleteUser", userId); log.info("影響行數(shù):{}", result); } } 運(yùn)行輸出: 14:26.711 [main] DEBUG z.m.chat01.UserMapper.deleteUser - ==> Preparing: DELETE FROM user WHERE id = ? 14:26.729 [main] DEBUG z.m.chat01.UserMapper.deleteUser - ==> Parameters: 1(Long) 14:26.811 [main] DEBUG z.m.chat01.UserMapper.deleteUser - <== Updates: 1 14:26.812 [main] INFO z.mybatis.chat01.UserMapperTest - 影響行數(shù):1 執(zhí)行查詢select語(yǔ)句有恩多屬性可以詳細(xì)的配置每一條SQL語(yǔ)句
需求:查詢所有用戶信息 UserMapper.xml中定義Select操作<!-- select用來(lái)定義一個(gè)查詢操作 id:操作的具體標(biāo)識(shí) resultType:指定查詢結(jié)果保存的類(lèi)型 --> <select id="getUserList" resultType="zhonghu.mybatis.chat01.UserModel"> <![CDATA[ SELECT * FROM user ]]> </select> 寫(xiě)法和update操作的寫(xiě)法類(lèi)似,指定id標(biāo)識(shí)、parameterType指定操作的參數(shù)類(lèi)型,resultType指定查詢結(jié)果的類(lèi)型,元素體中是具體的select語(yǔ)句。 調(diào)用SqlSession.select方法執(zhí)行更新操作UserTest添加一個(gè)用例: @Test public void getUserList() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { //執(zhí)行查詢操作 List<UserModel> userModelList = sqlSession.selectList("zhonghu.mybatis.chat01.UserMapper.getUserList"); log.info("結(jié)果:{}", userModelList); } } 多插入幾行,然后運(yùn)行上面的用例,輸出如下: 16:00.798 [main] DEBUG z.m.chat01.UserMapper.getUserList - ==> Preparing: SELECT * FROM user 16:00.817 [main] DEBUG z.m.chat01.UserMapper.getUserList - ==> Parameters: 16:00.829 [main] DEBUG z.m.chat01.UserMapper.getUserList - <== Total: 16 16:00.829 [main] INFO z.mybatis.chat01.UserMapperTest - 結(jié)果:[UserModel(id=2, name=修改冢狐, age=23, salary=50000.0), UserModel(id=3, name=修改冢狐, age=24, salary=6666.66), UserModel(id=4, name=Mybatis-1, age=19, salary=10000.0), UserModel(id=5, name=Java冢狐-2, age=25, salary=20000.0), UserModel(id=6, name=Mybatis-2, age=20, salary=20000.0), UserModel(id=7, name=Java冢狐-3, age=26, salary=30000.0), UserModel(id=8, name=Mybatis-3, age=21, salary=30000.0), UserModel(id=9, name=Java冢狐-4, age=27, salary=40000.0), UserModel(id=10, name=Mybatis-4, age=22, salary=40000.0), UserModel(id=11, name=Java冢狐-5, age=28, salary=50000.0), UserModel(id=12, name=Mybatis-5, age=23, salary=50000.0), UserModel(id=13, name=Java冢狐, age=1, salary=0.0), UserModel(id=14, name=冢狐, age=23, salary=50000.0), UserModel(id=59, name=Java冢狐, age=30, salary=50000.0), UserModel(id=69, name=Java冢狐, age=30, salary=50000.0), UserModel(id=89, name=Java冢狐, age=30, salary=50000.0)] Mapper接口的使用為什么需要Mapper接口上面我們講解了對(duì)一個(gè)表的增刪改查操作,都是通過(guò)調(diào)用SqlSession中的方法來(lái)完成的,大家再來(lái)看一下SqlSession接口中剛才用到的幾個(gè)方法的定義: int insert(String statement, Object parameter); int update(String statement, Object parameter); int delete(String statement, Object parameter); <E> List<E> selectList(String statement); 這些方法的特點(diǎn)我們來(lái)看一下:
這些都是使用過(guò)程中不方便的情況,想要方便的使用,就需要用到mybatis中的Mapper接口,我們可以定義一個(gè)interface,然后和Mapper xml關(guān)聯(lián)起來(lái),Mapper xml中的操作和Mapper接口中的方法會(huì)進(jìn)行綁定,當(dāng)我們調(diào)用Mapper接口的方法的時(shí)候,會(huì)間接調(diào)用到Mapper xml中的操作,接口的完整類(lèi)名需要和Mapper xml中的namespace一致。 Mapper接口的用法(三步)步驟1:定義Mapper接口去看一下,user.xml中的namespace,是: <mapper namespace="zhonghu.mybatis.chat01.UserMapper"> 我們創(chuàng)建的接口完整的名稱需要和上面的namespace的值一樣,下面我們創(chuàng)建一個(gè)接口UserMapper,如下: UserMapper.xml中有4個(gè)操作,我們需要在UserMapper接口中也定義4個(gè)操作,和UserMapper.xml的4個(gè)操作對(duì)應(yīng),如下: package zhonghu.mybatis.chat01; import java.util.List; public interface UserMapper { int insertUser(UserModel model); int updateUser(UserModel model); int deleteUser(Long userId); List<UserModel> getUserList(); } UserMapper接口中定義了4個(gè)方法,方法的名稱需要和UserMapper.xml具體操作的id值一樣,這樣調(diào)用UserMapper接口中的方法的時(shí)候,才會(huì)對(duì)應(yīng)的找到UserMapper.xml中具體的操作。 比如調(diào)用UserMapper接口中的insertUser方法,mybatis查找的規(guī)則是:通過(guò)接口完整名稱.方法名稱去Mapper xml中找到對(duì)應(yīng)的操作。 步驟2:通過(guò)SqlSession獲取Mapper接口對(duì)象SqlSession中有個(gè)getMapper方法,可以傳入接口的類(lèi)型,獲取具體的Mapper接口對(duì)象,如下: /** * Retrieves a mapper. * @param <T> the mapper type * @param type Mapper interface class * @return a mapper bound to this SqlSession */ <T> T getMapper(Class<T> type); 如獲取UserMapper接口對(duì)象: UserMapper mapper = sqlSession.getMapper(UserMapper.class); 步驟3:調(diào)用Mapper接口的方法對(duì)db進(jìn)行操作如調(diào)用UserMapper接口的insert操作: @Test public void insertUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); //創(chuàng)建UserModel對(duì)象 UserModel userModel = UserModel.builder().id(System.currentTimeMillis()).name("Java冢狐").age(30).salary(50000D).build(); //執(zhí)行插入操作 int insert = mapper.insertUser(userModel); log.info("影響行數(shù):{}", insert); } } 案例:使用Mapper接口來(lái)實(shí)現(xiàn)增刪改查創(chuàng)建一個(gè)測(cè)試類(lèi),代碼如下: package zhonghu.mybatis.chat01; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.List; @Slf4j public class UserMapperTest { private SqlSessionFactory sqlSessionFactory; @Before public void before() throws IOException { //指定mybatis全局配置文件 String resource = "mybatis-config.xml"; //讀取全局配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); //構(gòu)建SqlSessionFactory對(duì)象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); this.sqlSessionFactory = sqlSessionFactory; } @Test public void insertUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); //創(chuàng)建UserModel對(duì)象 UserModel userModel = UserModel.builder().id(System.currentTimeMillis()).name("Java冢狐").age(30).salary(50000D).build(); //執(zhí)行插入操作 int insert = mapper.insertUser(userModel); log.info("影響行數(shù):{}", insert); } } @Test public void updateUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); //創(chuàng)建UserModel對(duì)象 UserModel userModel = UserModel.builder().id(1L).name("Java冢狐,你好").age(18).salary(5000D).build(); //執(zhí)行更新操作 int result = mapper.updateUser(userModel); log.info("影響行數(shù):{}", result); } } @Test public void deleteUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); //定義需要?jiǎng)h除的用戶id Long userId = 1L; //執(zhí)行刪除操作 int result = mapper.deleteUser(userId); log.info("影響行數(shù):{}", result); } } @Test public void getUserList() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); //執(zhí)行查詢操作 List<UserModel> userModelList = mapper.getUserList(); userModelList.forEach(item -> { log.info("{}", item); }); } } } 大家認(rèn)真看一下上面的代碼,這次我們使用了UserMapper來(lái)間接調(diào)用UserMapper.xml中對(duì)應(yīng)的操作,可以去運(yùn)行一下感受一下效果。 Mapper接口使用時(shí)注意的幾點(diǎn)
Mapper接口的原理這個(gè)使用java中的動(dòng)態(tài)代理實(shí)現(xiàn)的,mybatis啟動(dòng)的時(shí)候會(huì)加載全局配置文件mybatis-config.xml,然后解析這個(gè)文件中的mapper元素指定的UserMapper.xml,會(huì)根據(jù)UserMapper.xml的namespace的值創(chuàng)建這個(gè)接口的一個(gè)動(dòng)態(tài)代理,具體可以去看一下mybatis的源碼,主要使用java中的Proxy實(shí)現(xiàn)的,使用java.lang.reflect.Proxy類(lèi)中的newProxyInstance方法,我們可以創(chuàng)建任意一個(gè)接口的一個(gè)代理對(duì)象: public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 我們使用Proxy來(lái)模仿Mapper接口的實(shí)現(xiàn): package zhonghu.mybatis.chat01; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; @Slf4j public class UserMapperTest { public static class UserMapperProxy implements InvocationHandler { private SqlSession sqlSession; private Class<?> mapperClass; public UserMapperProxy(SqlSession sqlSession, Class<?> mapperClass) { this.sqlSession = sqlSession; this.mapperClass = mapperClass; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.debug("invoke start"); String statement = mapperClass.getName() + "." + method.getName(); List<Object> result = sqlSession.selectList(statement); log.debug("invoke end"); return result; } } private SqlSessionFactory sqlSessionFactory; @Before public void before() throws IOException { //指定mybatis全局配置文件 String resource = "mybatis-config.xml"; //讀取全局配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); //構(gòu)建SqlSessionFactory對(duì)象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); this.sqlSessionFactory = sqlSessionFactory; } @Test public void test1() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(UserMapperTest.class.getClassLoader(), new Class[]{UserMapper.class}, new UserMapperProxy(sqlSession, UserMapper.class)); log.info("{}", userMapper.getUserList()); } } } 上面代碼中:UserMapper是沒(méi)有實(shí)現(xiàn)類(lèi)的,可以通過(guò)Proxy.newProxyInstance給UserMapper接口創(chuàng)建一個(gè)代理對(duì)象,當(dāng)調(diào)用UserMapper接口的方法的時(shí)候,會(huì)調(diào)用到UserMapperProxy對(duì)象的invoke方法。 運(yùn)行一下test1用例,輸出如下: 29:37.847 [main] DEBUG z.m.chat01.UserMapper.getUserList - ==> Preparing: SELECT * FROM user 29:37.865 [main] DEBUG z.m.chat01.UserMapper.getUserList - ==> Parameters: 29:37.878 [main] DEBUG z.m.chat01.UserMapper.getUserList - <== Total: 16 29:37.878 [main] DEBUG z.mybatis.chat01.UserMapperTest - invoke end 29:37.878 [main] INFO z.mybatis.chat01.UserMapperTest - [UserModel(id=2, name=修改冢狐, age=23, salary=50000.0), UserModel(id=3, name=修改冢狐, age=24, salary=6666.66), UserModel(id=4, name=Mybatis-1, age=19, salary=10000.0), UserModel(id=5, name=Java冢狐-2, age=25, salary=20000.0), UserModel(id=6, name=Mybatis-2, age=20, salary=20000.0), UserModel(id=7, name=Java冢狐-3, age=26, salary=30000.0), UserModel(id=8, name=Mybatis-3, age=21, salary=30000.0), UserModel(id=9, name=Java冢狐-4, age=27, salary=40000.0), UserModel(id=10, name=Mybatis-4, age=22, salary=40000.0), UserModel(id=11, name=Java冢狐-5, age=28, salary=50000.0), UserModel(id=12, name=Mybatis-5, age=23, salary=50000.0), UserModel(id=13, name=Java冢狐, age=1, salary=0.0), UserModel(id=14, name=冢狐, age=23, salary=50000.0), UserModel(id=59, name=Java冢狐, age=30, salary=50000.0), UserModel(id=69, name=Java冢狐, age=30, salary=50000.0), UserModel(id=89, name=Java冢狐, age=30, salary=50000.0)] 注意上面輸出的invoke start和invoke end,可以看到我們調(diào)用userMapper.getUserList時(shí)候,被UserMapperProxy#invoke方法處理了。 Mybatis中創(chuàng)建Mapper接口代理對(duì)象使用的是下面這個(gè)類(lèi),大家可以去研究一下: public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return mapperInterface; } public Map<Method, MapperMethod> getMethodCache() { return methodCache; } @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } } |
|
來(lái)自: Coder編程 > 《待分類(lèi)》