Mybatis系列目標:從入門開始開始掌握一個高級開發所需要的Mybatis技能。
這是mybatis系列第3篇。
主要內容
1、 快速入門
-
準備數據庫
-
我們的需求
-
使用idea創建項目
-
pom.xml中引入mybatis依賴
-
配置mybatis全局配置文件
-
創建Mapper xml文件
-
mybatis全局配置文件中引入Mapper xml文件
-
構建SqlSessionFactory對象
-
構建SqlSession對象
-
引入lombok(非必須)
-
引入logback支持(非必須)
-
寫一個測試用例
2、 使用SqlSesion執行sql操作
-
SqlSession常見的用法
-
新增操作
-
執行刪除
-
執行修改
-
執行查詢
3、 Mapper接口的使用
-
爲什麼需要Mapper接口
-
Mapper接口的用法
-
案例:使用Mapper接口來實現增刪改查
-
Mapper接口使用時注意的幾點
-
Mapper接口的原理
4、 案例源碼獲取方式
快速入門
準備數據庫
mysql中執行下面sql:
/*創建數據庫javacode2018*/
DROP DATABASE IF EXISTS `javacode2018`;
CREATE DATABASE `javacode2018`;
USE `javacode2018`;
/*創建表結構*/
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE t_user (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主鍵,用戶id,自動增長',
`name` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '姓名',
`age` SMALLINT NOT NULL DEFAULT 1 COMMENT '年齡',
`salary` DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT '薪水',
`sex` TINYINT NOT NULL DEFAULT 0 COMMENT '性別,0:未知,1:男,2:女'
) COMMENT '用戶表';
SELECT * FROM t_user;
上面我們創建了一個數據庫:javacode2018
,一個用戶表t_user
。
我們的需求
使用mybatis來實現對t_user
表增刪改查。
使用idea創建項目
我們在上一篇文章mybatis-series
項目中創建另外一個模塊chat02
,過程如下:
選中mybatis-series,如下圖:
點擊右鍵->New->Module
,如下圖:
選中上圖中的Maven
,點擊Next
,如下圖:
出現下面窗口:
上圖中輸入ArtifactId
爲chat02
,點擊Next
,如下圖:
點擊上圖中的Finish
完成chat02
模塊的創建,項目結構如下圖:
pom.xml中引入mybatis依賴
<dependencies>
<!-- mybatis依賴 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<!-- mysql 驅動 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- lombok支持 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 單元測試junit支持 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<!-- 引入logback用來輸出日誌 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
</dependencies>
上面我們引入了依賴mybatis、mysql驅動、lombok支持、junit、logback支持
,其實運行mybatis
只需要引入下面這一個構件就行了:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
注意:上面pom引入的構建中沒有寫版本號,是因爲構件的版本號在父pom.xml
中已經聲明瞭,所以chat03/pom.xml
中就不需要再去寫了。
配置mybatis全局配置文件
使用mybatis操作數據庫,那麼當然需要配置數據庫相關信息,這個需要在mybatis全局配置文件中進行配置。
mybatis需提供一個全局配置的xml文件,可以在這個配置文件中對mybatis進行配置,如事務的支持,數據源的配置等等,這個屬於配置文件,我們一般放在main/resource
中。
在chat03/src/main/resource
中創建mybatis-config.xml
文件,內容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 環境配置,可以配置多個環境 -->
<environments default="chat03">
<!--
environment用來對某個環境進行配置
id:環境標識,唯一
-->
<environment id="chat03">
<!-- 事務管理器工廠配置 -->
<transactionManager type="org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory"/>
<!-- 數據源工廠配置,使用工廠來創建數據源 -->
<dataSource type="org.apache.ibatis.datasource.pooled.PooledDataSourceFactory">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root123"/>
</dataSource>
</environment>
</environments>
</configuration>
我們做一下解釋。
configuration元素
這個是mybatis全局配置文件的根元素,每個配置文件只有一個
environments元素
用來配置mybatis的環境信息,什麼是環境?比如開發環境、測試環境、線上環境,這3個環境中的數據庫可能是不一樣的,可能還有更多的環境。
environments元素中用來配置多個環境的,具體的一個環境使用environment
元素進行配置,environment元素有個id用來標識某個具體的環境。
配置了這麼多環境,那麼mybatis具體會使用哪個呢?
environments
元素有個default
屬性,用來指定默認使用哪個環境,如上面默認使用的是chat03
。
environment元素
用來配置具體的環境信息,這個元素下面有兩個子元素:transactionManager和dataSource
-
transactionManager元素
用來配置事務工廠的,有個type屬性,type的值必須是
org.apache.ibatis.transaction.TransactionFactory
接口的實現類,TransactionFactory
看名字就知道是一個工廠,用來創建事務管理器org.apache.ibatis.transaction.Transaction
對象的,TransactionFactory
接口默認有2個實現:org.apache.ibatis.transaction.managed.ManagedTransactionFactory org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory
一般情況下我們使用
org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory
這個,mybatis和其他框架集成,比如和spring集成,事務交由spring去控制,spring中有TransactionFactory
接口的一個實現org.mybatis.spring.transaction.SpringManagedTransactionFactory
,有興趣的朋友可以去研究一下,這個到時候講到spring的使用會詳細說。 -
dataSource元素
這個用來配置數據源的,type屬性的值必須爲接口
org.apache.ibatis.datasource.DataSourceFactory
的實現類,DataSourceFactory
也是一個工廠,用來創建數據源javax.sql.DataSource
對象的,mybatis中這個接口默認有3個實現類:org.apache.ibatis.datasource.jndi.JndiDataSourceFactory org.apache.ibatis.datasource.pooled.PooledDataSourceFactory org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory
我們使用第2個
org.apache.ibatis.datasource.pooled.PooledDataSourceFactory
,這個用來創建一個數據庫連接池類型的數據源,可以實現數據庫連接共用,減少連接重複創建銷燬的時間。配置數據源需要指定數據庫連接的屬性信息,比如:驅動、連接db的url、用戶名、密碼,這個在
dataSource元素
下面的property
中配置,property
元素的格式:<property name="屬性名稱" value="值"/>
創建Mapper xml文件
我們需要對t_user表進行操作,需要寫sql,sql寫在什麼地方呢?
在mybatis中一般我們將一個表的所有sql操作寫在一個mapper xml中,一般命名爲XXXMapper.xml
格式。
創建文件chat02/src/main/resource/mapper/UserMapper.xml
,內容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.javacode2018.chat02.UserMapper">
</mapper>
mapper xml
根元素爲mapper
,這個元素有個namespace
屬性,系統中會有很多表,每個表對應一個Mapper xml
,爲了防止mapper文件重複,我們需要給每個mapper xml文件需要指定一個namespace,通過這個可以區分每個mapper xml文件,上面我們指定爲com.javacode2018.chat02.UserMapper
。
一會對t_user
表的所有操作相關的sql,我們都會寫在上面這個xml中。
mybatis全局配置文件中引入Mapper xml文件
UserMapper.xml我們寫好了,如何讓mybatis知道這個文件呢,此時我們需要在mybatis-config.xml
全局配置文件中引入UserMapper.xml
,在mybatis-config.xml
加入下面配置:
<mappers>
<mapper resource="mapper/UserMapper.xml" />
</mappers>
mappers元素
下面有多個mapper
元素,通過mapper元素
的resource屬性
可以引入Mapper xml文件,resource
是相對於classes的路徑。
上面說的都是一些配置文件,配置文件都ok了,下面我們就需要將mybatis跑起來了,此時需要使用到mybatis中的一些java對象了。
構建SqlSessionFactory對象
//指定mybatis全局配置文件
String resource = "mybatis-config.xml";
//讀取全局配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//構建SqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactory是一個接口,是一個重量級的對象,SqlSessionFactoryBuilder通過讀取全局配置文件來創建一個SqlSessionFactory
,創建這個對象是比較耗時的,主要耗時在對mybatis全局配置文件的解析上面,全局配置文件中包含很多內容,SqlSessionFactoryBuilder通過解析這些內容,創建了一個複雜的SqlSessionFactory
對象,這個對象的生命週期一般和應用的生命週期是一樣的,隨着應用的啓動而創建,隨着應用的停止而結束,所以一般是一個全局對象,一般情況下一個db對應一個SqlSessionFactory對象。
構建SqlSession對象
SqlSession相當於jdbc中的Connection對象,相當於數據庫的一個連接,可以用SqlSession來對db進行操作:如執行sql、提交事務、關閉連接等等,需要通過SqlSessionFactory
來創建SqlSession
對象,SqlSessionFactory
中常用的有2個方法來創建SqlSession對象
,如下:
//創建一個SqlSession,默認不會自動提交事務
SqlSession openSession();
//創建一個SqlSession,autoCommit:指定是否自動提交事務
SqlSession openSession(boolean autoCommit);
SqlSession接口中很多方法,直接用來操作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
開頭的可以對db進行查詢操作,insert
相關的可以對db進行插入操作,update相關的可以對db進行更新操作。
引入lombok支持(非必須)
聲明一下:lombok不是mybatis必須的,爲了簡化代碼而使用的,以後我們會經常使用。
Lombok能以簡單的註解形式來簡化java代碼,提高開發人員的開發效率。例如開發中經常需要寫的javabean,都需要花時間去添加相應的getter/setter,也許還要去寫構造器、equals等方法,而且需要維護,當屬性多時會出現大量的getter/setter方法,這些顯得很冗長也沒有太多技術含量,一旦修改屬性,就容易出現忘記修改對應方法的失誤。
Lombok能通過註解的方式,在編譯時自動爲屬性生成構造器、getter/setter、equals、hashcode、toString方法。出現的神奇就是在源碼中沒有getter和setter方法,但是在編譯生成的字節碼文件中有getter和setter方法。這樣就省去了手動重建這些代碼的麻煩,使代碼看起來更簡潔些。
lombok的使用步驟
-
先在idea中安裝lombok插件
打開idea,點擊
File->Settings->plugins
,然後搜索Lombok Plugin
,點擊安裝就可以了。 -
maven中引入lombok支持
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <scope>provided</scope> </dependency>
-
代碼中使用lombok相關功能
引入logback(非必須)
聲明一下:日誌框架mybatis中也不是必須的,不用配置也可以正常運行。
爲了方便查看mybatis運行過程中產生的日誌,比如:執行的sql、sql的參數、sql的執行結果等等調試信息,我們需要引入日誌框架的支持,logback是一個很好的日誌框架,此處我們就使用這個
mybatis中集成logback步驟
-
maven中引入logback支持
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
-
src/main/resources中創建
logback.xml
文件:<?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="com.javacode2018" level="debug" additivity="false"> <appender-ref ref="STDOUT" /> </logger> </configuration>
logback.xml具體的寫法不是本文討論的範圍,有興趣的朋友可以去研究一下logback具體的用法。
上面xml中配置了
com.javacode2018
包中所有的類,使用logback輸出日誌的時候,debug級別及以上級別的日誌會輸出到控制檯,方便我們查看。
寫一個測試用例
在chat02/src/test
下創建一個類:
com.javacode2018.chat02.UserTest
內容如下:
package com.javacode2018.chat02;
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 UserTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
//指定mybatis全局配置文件
String resource = "mybatis-config.xml";
//讀取全局配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//構建SqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
this.sqlSessionFactory = sqlSessionFactory;
}
@Test
public void sqlSession() {
SqlSession sqlSession = this.sqlSessionFactory.openSession();
log.info("{}", sqlSession);
}
}
上面代碼中有個@Slf4j
註解,這個是lombok提供的,可以在這個類中生成下面代碼:
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserTest.class);
運行一下上面的用例:sqlSession
方法,輸出如下:
45:51.289 [main] INFO com.javacode2018.chat02.UserTest - org.apache.ibatis.session.defaults.DefaultSqlSession@1f021e6c
使用SqlSesion執行sql操作
SqlSession常見的用法
SqlSession相當於一個連接,可以使用這個對象對db執行增刪改查操作,操作完畢之後需要關閉,使用步驟:
1.獲取SqlSession對象:通過該sqlSessionFactory.openSession方法獲取SqlSession對象
2.對db進行操作:使用SqlSession對象進行db操作
3.關閉SqlSession對象:sqlSession.close();
常見的使用方式如下:
//獲取SqlSession
SqlSession sqlSession = this.sqlSessionFactory.openSession();
try {
//執行業務操作,如:增刪改查
} finally {
//關閉SqlSession
sqlSession.close();
}
上面我們將SqlSession的關閉放在finally
塊中,確保close()一定會執行。更簡單的方式是使用java中的try()
的方式,如下:
try (SqlSession sqlSession = this.sqlSessionFactory.openSession();) {
//執行業務操作,如:增刪改查
}
新增操作
需求:傳入UserModel
對象,然後將這個對象的數據插入到t_user
表中。
創建一個`UserModel`
新建一個com.javacode2018.chat02.UserModel
類,代碼如下:
package com.javacode2018.chat02;
import lombok.*;
/**
* 公衆號:路人甲Java,工作10年的前阿里P7分享Java、算法、數據庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class UserModel {
private Long id;
private String name;
private Integer age;
private Double salary;
private Integer sex;
}
這個類的字段和t_user
表對應。
UserMapper.xml中定義插入操作
我們說過了,對t_user
表的所有sql操作,我們都放在UserMapper.xml
中,我們在UserMapper.xml
中加入下面配置,使用insert元素
定義插入操作:
<!-- insert用來定義一個插入操作
id:操作的具體標識
parameterType:指定插入操作接受的參數類型
-->
<insert id="insertUser" parameterType="com.javacode2018.chat02.UserModel">
<![CDATA[
INSERT INTO t_user (id,name,age,salary,sex) VALUES (#{id},#{name},#{age},#{salary},#{sex})
]]>
</insert>
insert
元素用來定義了一個對db的insert操作
id:是這個操作的一個標識,一會通過mybatis執行操作的時候會通過這個namespace和id引用到這個insert操作,
parameterType:用來指定這個insert操作接受的參數的類型,可以是:各種javabean、map、list、collection類型的java對象,我們這個插入接受的是UserModel
對象。
insert元素內部定義了具體的sql,可以看到是一個insert的sql,向t_user表插入數據。
需要插入的值從UserModel對象中獲取,取UserModel
對象的的字段,使用#{字段}這種格式可以獲取到UserModel中字段的值。
調用SqlSession.insert方法執行插入操作
t_user插入的sql我們已經在UserMapper中寫好,此時我們怎麼調用呢?
需要調用SqlSession.insert
方法:
int insert(String statement, Object parameter)
這個方法有2個參數:
statement:表示那個操作,值爲Mapper xml的namespace.具體操作的id
,如需要調用UserMapper.xml
中的insertUser
操作,這個值就是:
com.javacode2018.chat02.UserMapper.insertUser
parameter:insert操作的參數,和Mapper xml中的insert中的parameterType
指定的類型一致。
返回值爲插入的行數。
UserTest
類中新增一個測試用例:
@Test
public void insertUser() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(false);) {
//創建UserModel對象
UserModel userModel = UserModel.builder().id(2L).name("javacode2018").age(30).salary(50000D).sex(1).build();
//執行插入操作
int result = sqlSession.insert("com.javacode2018.chat02.UserMapper.insertUser", userModel);
log.info("插入影響行數:{}", result);
//提交事務
sqlSession.commit();
}
}
運行輸出如下:
01:46.683 [main] DEBUG c.j.chat02.UserMapper.insertUser - ==> Preparing: INSERT INTO t_user (id,name,age,salary,sex) VALUES (?,?,?,?,?)
01:46.745 [main] DEBUG c.j.chat02.UserMapper.insertUser - ==> Parameters: 2(Long), javacode2018(String), 30(Integer), 50000.0(Double), 1(Integer)
01:46.751 [main] DEBUG c.j.chat02.UserMapper.insertUser - <== Updates: 1
01:46.751 [main] INFO com.javacode2018.chat02.UserTest - 影響行數:1
輸出中打印了詳細的sql語句,以及sql的參數信息,可以看到Mapper xml中的
#{}
被替換爲了?
,這個使用到了jdbc中的PreparedStatement
來對參數設置值。輸出中的第二行詳細列出了參數的值以及每個值的類型。
第三行輸出了insert的結果爲1,表示插入成功了1行記錄。
去db中看一下,如下,插入成功:
mysql> SELECT * FROM t_user;
+----+---------------+-----+----------+-----+
| id | name | age | salary | sex |
+----+---------------+-----+----------+-----+
| 1 | 路人甲Java | 30 | 50000.00 | 1 |
+----+---------------+-----+----------+-----+
1 row in set (0.00 sec)
上面代碼中創建SqlSession,我們使用的是sqlSessionFactory.openSession()
創建的,這個方法創建的SqlSession,內部事務是非自動提交的方式,所以需要我們手動提交:
sqlSession.commit();
如果想自動提交事務,可以將上面的測試用例改成下面這樣:
@Test
public void insertUser() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
//創建UserModel對象
UserModel userModel = UserModel.builder().id(1L).name("路人甲Java").age(30).salary(50000D).sex(1).build();
//執行插入操作
int result = sqlSession.insert("com.javacode2018.chat02.UserMapper.insertUser", userModel);
log.info("影響行數:{}", result);
}
}
上面在創建SqlSession的時候調用了sqlSessionFactory.openSession(true)
,指定事務爲自動提交模式,所以最後我們不需要手動提交事務了。
更新操作
需求:傳入UserModel
對象,然後通過id更新數據。
UserMapper.xml中定義Update操作
使用update定義更新操作:
<!-- update用來定義一個更新操作
id:操作的具體標識
parameterType:指定操作接受的參數類型
-->
<update id="updateUser" parameterType="com.javacode2018.chat02.UserModel">
<![CDATA[
UPDATE t_user SET name = #{name},age = #{age},salary = #{salary},sex = #{sex} WHERE id = #{id}
]]>
</update>
寫法和insert操作的寫法類似,指定id標識、parameterType指定操作的參數類型,元素體中是具體的sql語句。
調用SqlSession.update方法執行更新操作
需要調用SqlSession.update
方法:
int update(String statement, Object parameter)
這個方法有2個參數:
statement:表示哪個操作,值爲Mapper xml的namespace.具體操作的id
,如需要調用UserMapper.xml
中的updateUser
操作,這個值就是:
com.javacode2018.chat02.UserMapper.updateUser
parameter:update操作的參數,和Mapper xml中的update中的parameterType
指定的類型一致。
返回值爲update影響行數。
UserTest
類中新增一個測試用例:
@Test
public void updateUser() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
//創建UserModel對象
UserModel userModel = UserModel.builder().id(1L).name("路人甲Java,你好").age(18).salary(5000D).sex(0).build();
//執行更新操作
int result = sqlSession.update("com.javacode2018.chat02.UserMapper.updateUser", userModel);
log.info("影響行數:{}", result);
}
}
運行輸出:
14:09.051 [main] DEBUG c.j.chat02.UserMapper.updateUser - ==> Preparing: UPDATE t_user SET name = ?,age = ?,salary = ?,sex = ? WHERE id = ?
14:09.095 [main] DEBUG c.j.chat02.UserMapper.updateUser - ==> Parameters: 路人甲Java,你好(String), 18(Integer), 5000.0(Double), 0(Integer), 1(Long)
14:09.100 [main] DEBUG c.j.chat02.UserMapper.updateUser - <== Updates: 1
14:09.101 [main] INFO com.javacode2018.chat02.UserTest - 影響行數:1
db中去看一下:
mysql> SELECT * FROM t_user;
+----+------------------------+-----+----------+-----+
| id | name | age | salary | sex |
+----+------------------------+-----+----------+-----+
| 1 | 路人甲Java,你好 | 18 | 5000.00 | 0 |
| 2 | javacode2018 | 30 | 50000.00 | 1 |
+----+------------------------+-----+----------+-----+
2 rows in set (0.00 sec)
刪除操作
需求:根據用戶的id刪除對應的用戶記錄
UserMapper.xml中定義Delete操作
使用update元素定義刪除操作:
<!-- update用來定義一個刪除操作
id:操作的具體標識
parameterType:指定操作接受的參數類型
-->
<update id="deleteUser" parameterType="java.lang.Long">
<![CDATA[
DELETE FROM t_user WHERE id = #{id}
]]>
</update>
寫法和update操作的寫法類似,指定id標識、parameterType指定操作的參數類型,用戶id爲Long
類型的,元素體中是具體的delete語句。
調用SqlSession.update方法執行更新操作
需要調用SqlSession.delete
方法:
int delete(String statement, Object parameter)
這個方法有2個參數:
statement:表示哪個操作,值爲Mapper xml的namespace.具體操作的id
,如需要調用UserMapper.xml
中的deleteUser
操作,這個值就是:
com.javacode2018.chat02.UserMapper.
parameter:delete操作的參數,和Mapper xml中的delete中的parameterType
指定的類型一致。
返回值爲delete影響行數。
UserTest
類中新增一個測試用例:
@Test
public void deleteUser() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
//定義需要刪除的用戶id
Long userId = 1L;
//執行刪除操作
int result = sqlSession.delete("com.javacode2018.chat02.UserMapper.deleteUser", userId);
log.info("影響行數:{}", result);
}
}
運行輸出:
24:45.427 [main] DEBUG c.j.chat02.UserMapper.deleteUser - ==> Preparing: DELETE FROM t_user WHERE id = ?
24:45.476 [main] DEBUG c.j.chat02.UserMapper.deleteUser - ==> Parameters: 1(Long)
24:45.485 [main] DEBUG c.j.chat02.UserMapper.deleteUser - <== Updates: 1
24:45.485 [main] INFO com.javacode2018.chat02.UserTest - 影響行數:1
執行查詢
需求:查詢所有用戶信息
UserMapper.xml中定義Select操作
<!-- select用來定義一個查詢操作
id:操作的具體標識
resultType:指定查詢結果保存的類型
-->
<select id="getUserList" resultType="com.javacode2018.chat02.UserModel">
<![CDATA[
SELECT * FROM t_user
]]>
</select>
寫法和update操作的寫法類似,指定id標識、parameterType指定操作的參數類型,resultType指定查詢結果的類型,元素體中是具體的select語句。
調用SqlSession.select方法執行更新操作
UserTest添加一個用例:
@Test
public void getUserList() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
//執行查詢操作
List<UserModel> userModelList = sqlSession.selectList("com.javacode2018.chat02.UserMapper.getUserList");
log.info("結果:{}", userModelList);
}
}
多插入幾行,然後運行上面的用例,輸出如下:
36:39.015 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==> Preparing: SELECT * FROM t_user
36:39.048 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==> Parameters:
36:39.066 [main] DEBUG c.j.chat02.UserMapper.getUserList - <== Total: 3
36:39.067 [main] INFO com.javacode2018.chat02.UserTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
36:39.069 [main] INFO com.javacode2018.chat02.UserTest - UserModel(id=1575621274235, name=路人甲Java, age=30, salary=50000.0, sex=1)
36:39.069 [main] INFO com.javacode2018.chat02.UserTest - UserModel(id=1575621329823, name=路人甲Java, age=30, salary=50000.0, sex=1)
Mapper接口的使用
爲什麼需要Mapper接口
上面我們講解了對一個表的增刪改查操作,都是通過調用SqlSession中的方法來完成的,大家再來看一下SqlSession接口中剛纔用到的幾個方法的定義:
int insert(String statement, Object parameter);
int update(String statement, Object parameter);
int delete(String statement, Object parameter);
<E> List<E> selectList(String statement);
這些方法的特點我們來看一下:
-
調用這些方法,需要明確知道
statement
的值,statement的值爲namespace.具體操作的id
,這些需要打開Mapper xml
中去查看了才知道,寫起來不方便 -
parameter參數都是Object類型的,我們根本不知道這個操作具體類型是什麼,需要查看
Mapper xml
才知道,隨便傳遞個值,可能類型不匹配,但是隻有在運行的時候才知道有問題 -
selectList方法返回的是一個泛型類型的,通過這個方法我們根本不知道返回的結果的具體類型,也需要去查看
Mapper xml
才知道
以上這幾點使用都不是太方便,有什麼方法能解決上面這些問題麼?
有,這就是mybatis中的Mapper接口,我們可以定義一個interface,然後和Mapper xml關聯起來,Mapper xml中的操作和Mapper接口中的方法會進行綁定,當我們調用Mapper接口的方法的時候,會間接調用到Mapper xml中的操作,接口的完整類名需要和Mapper xml中的namespace一致。
Mapper接口的用法(三步)
步驟1:定義Mapper接口
去看一下,UserMapper.xml中的namespace,是:
<mapper namespace="com.javacode2018.chat02.UserMapper">
我們創建的接口完整的名稱需要和上面的namespace的值一樣,下面我們創建一個接口com.javacode2018.chat02.UserMapper
,如下:
package com.javacode2018.chat02;
/**
* 公衆號:路人甲Java,工作10年的前阿里P7分享Java、算法、數據庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!
*/
public interface UserMapper {
}
UserMapper.xml中有4個操作,我們需要在UserMapper接口中也定義4個操作,和UserMapper.xml的4個操作對應,如下:
package com.javacode2018.chat02;
import java.util.List;
/**
* 公衆號:路人甲Java,工作10年的前阿里P7分享Java、算法、數據庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!
*/
public interface UserMapper {
int insertUser(UserModel model);
int updateUser(UserModel model);
int deleteUser(Long userId);
List<UserModel> getUserList();
}
UserMapper接口中定義了4個方法,方法的名稱需要和UserMapper.xml具體操作的id值一樣,這樣調用UserMapper接口中的方法的時候,纔會對應的找到UserMapper.xml中具體的操作。
比如調用UserMapper
接口中的insertUser
方法,mybatis查找的規則是:通過接口完整名稱.方法
名稱去Mapper xml中找到對應的操作。
步驟2:通過SqlSession獲取Mapper接口對象
SqlSession中有個getMapper
方法,可以傳入接口的類型,獲取具體的Mapper接口對象,如下:
/**
* 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接口對象:
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
步驟3:調用Mapper接口的方法對db進行操作
如調用UserMapper接口的insert操作:
@Test
public void insertUser() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//創建UserModel對象
UserModel userModel = UserModel.builder().id(System.currentTimeMillis()).name("路人甲Java").age(30).salary(50000D).sex(1).build();
//執行插入操作
int insert = mapper.insertUser(userModel);
log.info("影響行數:{}", insert);
}
}
案例:使用Mapper接口來實現增刪改查
chat02/src/test/java中創建一個測試類,代碼如下:
package com.javacode2018.chat02;
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;
/**
* 公衆號:路人甲Java,工作10年的前阿里P7分享Java、算法、數據庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!
*/
@Slf4j
public class UserMapperTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
//指定mybatis全局配置文件
String resource = "mybatis-config.xml";
//讀取全局配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//構建SqlSessionFactory對象
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);
//創建UserModel對象
UserModel userModel = UserModel.builder().id(System.currentTimeMillis()).name("路人甲Java").age(30).salary(50000D).sex(1).build();
//執行插入操作
int insert = mapper.insertUser(userModel);
log.info("影響行數:{}", insert);
}
}
@Test
public void updateUser() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//創建UserModel對象
UserModel userModel = UserModel.builder().id(1L).name("路人甲Java,你好").age(18).salary(5000D).sex(0).build();
//執行更新操作
int result = mapper.updateUser(userModel);
log.info("影響行數:{}", result);
}
}
@Test
public void deleteUser() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//定義需要刪除的用戶id
Long userId = 1L;
//執行刪除操作
int result = mapper.deleteUser(userId);
log.info("影響行數:{}", result);
}
}
@Test
public void getUserList() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//執行查詢操作
List<UserModel> userModelList = mapper.getUserList();
userModelList.forEach(item -> {
log.info("{}", item);
});
}
}
}
大家認真看一下上面的代碼,這次我們使用了UserMapper來間接調用UserMapper.xml
中對應的操作,可以去運行一下感受一下效果。
Mapper接口使用時注意的幾點
-
Mapper接口的完整類名必須和對應的Mapper xml中的namespace的值一致
-
Mapper接口中方法的名稱需要和Mapper xml中具體操作的id值一致
-
Mapper接口中方法的參數、返回值可以不和Mapper xml中的一致
Mapper接口的原理
這個使用java中的動態代理實現的,mybatis啓動的時候會加載全局配置文件mybatis-config.xml
,然後解析這個文件中的mapper元素指定的UserMapper.xml
,會根據UserMapper.xml的namespace的值
創建這個接口的一個動態代理,具體可以去看一下mybatis的源碼,主要使用java中的Proxy實現的,使用java.lang.reflect.Proxy
類中的newProxyInstance
方法,我們可以創建任意一個接口的一個代理對象:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
我們使用Proxy來模仿Mapper接口的實現:
package com.javacode2018.chat02;
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;
/**
* 公衆號:路人甲Java,工作10年的前阿里P7分享Java、算法、數據庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!
*/
@Slf4j
public class ProxyTest {
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);
//構建SqlSessionFactory對象
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(ProxyTest.class.getClassLoader(), new Class[]{UserMapper.class}, new UserMapperProxy(sqlSession, UserMapper.class));
log.info("{}", userMapper.getUserList());
}
}
}
上面代碼中:UserMapper是沒有實現類的,可以通過Proxy.newProxyInstance給UserMapper
接口創建一個代理對象,當調用UserMapper
接口的方法的時候,會調用到UserMapperProxy
對象的invoke
方法。
運行一下test1
用例,輸出如下:
16:34.288 [main] DEBUG com.javacode2018.chat02.ProxyTest - invoke start
16:34.555 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==> Preparing: SELECT * FROM t_user
16:34.580 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==> Parameters:
16:34.597 [main] DEBUG c.j.chat02.UserMapper.getUserList - <== Total: 4
16:34.597 [main] DEBUG com.javacode2018.chat02.ProxyTest - invoke end
16:34.597 [main] INFO com.javacode2018.chat02.ProxyTest - [UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1), UserModel(id=1575621274235, name=路人甲Java, age=30, salary=50000.0, sex=1), UserModel(id=1575621329823, name=路人甲Java, age=30, salary=50000.0, sex=1), UserModel(id=1575623283897, name=路人甲Java, age=30, salary=50000.0, sex=1)]
注意上面輸出的invoke start
和invoke end
,可以看到我們調用userMapper.getUserList
時候,被UserMapperProxy#invoke
方法處理了。
Mybatis中創建Mapper接口代理對象使用的是下面這個類,大家可以去研究一下:
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);
}
}