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);
-
}
-
}
案例代码获取方式
扫码添加微信备注:mybatis案例,即可获取
MyBatis系列
更多好文章
感谢大家的阅读,也欢迎您把这篇文章分享给更多的朋友一起阅读!谢谢!
路人甲java
▲长按图片识别二维码关注
路人甲Java:工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!来源:https://itsoku.blog.csdn.net/article/details/103431747