Java充电社
专辑
博文
联系我
本人继续续收门徒,亲手指导
MyBatis教程
-> MyBatis使用详解(-)
1、MyBatis未出世之前我们那些痛苦的经历
2、MyBatis入门,你确定mybatis你玩的很溜?
3、MyBatis使用详解(-)
4、MyBatis使用详解(2)
5、详解传参的几种方式、原理、源码解析
6、增删改知识点汇总及主键获取3种方式详解
7、种查询详解
8、自动映射,使用需谨慎
9、延迟加载、鉴别器、继承
10、动态SQL,这么多种你都会么?
11、类型处理器
12、掌握缓存为查询提速!
上一篇:MyBatis入门,你确定mybatis你玩的很溜?
下一篇:MyBatis使用详解(2)
<div style="display:none"></div> ## Mybatis系列第3篇:Mybatis使用详解(-) ### 主要内容 1. **快速入门** 2. **使用SqlSesion执行sql操作** 3. **Mapper接口的使用** 4. **案例源码获取方式** ### 快速入门 #### 准备数据库 mysql中执行下面sql: ```java /*创建数据库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,如下图: ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/74/dfcb1701-a359-420e-9ccf-3bd43d819bc3.png) 点击`右键->New->Module`,如下图: ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/74/e35c3adb-aaaa-41ed-8da4-62f51f393f8b.png) ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/74/8d4c1438-2aea-4491-8243-f7f3e0aa89ea.png) 选中上图中的`Maven`,点击`Next`,如下图: ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/74/47181e94-b01c-4fd2-a8e6-e90d3f841c66.png) 出现下面窗口: ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/74/b704d606-df5b-4e0e-a594-dae058a18fcf.png) 上图中输入`ArtifactId`为`chat02`,点击`Next`,如下图: ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/74/d6e873f4-d5f3-44a9-9632-2f72d2b3bea1.png) 点击上图中的`Finish`完成`chat02`模块的创建,项目结构如下图: ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/74/bcf8a426-8a3b-483a-ac42-524ef31c94a5.png) #### pom.xml中引入mybatis依赖 ```xml <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`只需要引入下面这一个构件就行了: ```xml <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 <?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个实现: ```java 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个实现类: ```java 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`元素的格式: ```xml <property name="属性名称" value="值"/> ``` #### 创建Mapper xml文件 我们需要对t_user表进行操作,需要写sql,sql写在什么地方呢? 在mybatis中一般我们将一个表的所有sql操作写在一个mapper xml中,一般命名为`XXXMapper.xml`格式。 创建文件`chat02/src/main/resource/mapper/UserMapper.xml`,内容如下: ```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`加入下面配置: ```xml <mappers> <mapper resource="mapper/UserMapper.xml" /> </mappers> ``` `mappers元素`下面有多个`mapper`元素,通过`mapper元素`的`resource属性`可以引入Mapper xml文件,`resource`是相对于classes的路径。 上面说的都是一些配置文件,配置文件都ok了,下面我们就需要将mybatis跑起来了,此时需要使用到mybatis中的一些java对象了。 #### 构建SqlSessionFactory对象 ```java //指定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对象`,如下: ```java //创建一个SqlSession,默认不会自动提交事务 SqlSession openSession(); //创建一个SqlSession,autoCommit:指定是否自动提交事务 SqlSession openSession(boolean autoCommit); ``` SqlSession接口中很多方法,直接用来操作db,方法清单如下,大家眼熟一下: ```java <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的使用步骤 1. 先在idea中安装lombok插件 打开idea,点击`File->Settings->plugins`,然后搜索`Lombok Plugin`,点击安装就可以了。 2. maven中引入lombok支持 ```xml <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <scope>provided</scope> </dependency> ``` 3. 代码中使用lombok相关功能 #### 引入logback(非必须) **声明一下:日志框架mybatis中也不是必须的,不用配置也可以正常运行。** 为了方便查看mybatis运行过程中产生的日志,比如:执行的sql、sql的参数、sql的执行结果等等调试信息,我们需要引入日志框架的支持,logback是一个很好的日志框架,此处我们就使用这个 ##### mybatis中集成logback步骤 1. maven中引入logback支持 ```xml <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> ``` 2. src/main/resources中创建`logback.xml`文件: ```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`下创建一个类: ```java com.javacode2018.chat02.UserTest ``` 内容如下: ```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; @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提供的,可以在这个类中生成下面代码: ```java private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserTest.class); ``` 运行一下上面的用例:`sqlSession`方法,输出如下: ```java 45:51.289 [main] INFO com.javacode2018.chat02.UserTest - org.apache.ibatis.session.defaults.DefaultSqlSession@1f021e6c ``` ### 使用SqlSesion执行sql操作 #### SqlSession常见的用法 SqlSession相当于一个连接,可以使用这个对象对db执行增删改查操作,操作完毕之后需要关闭,使用步骤: ```java 1.获取SqlSession对象:通过该sqlSessionFactory.openSession方法获取SqlSession对象 2.对db进行操作:使用SqlSession对象进行db操作 3.关闭SqlSession对象:sqlSession.close(); ``` 常见的使用方式如下: ```java //获取SqlSession SqlSession sqlSession = this.sqlSessionFactory.openSession(); try { //执行业务操作,如:增删改查 } finally { //关闭SqlSession sqlSession.close(); } ``` 上面我们将SqlSession的关闭放在`finally`块中,确保close()一定会执行。更简单的方式是使用java中的`try()`的方式,如下: ```java try (SqlSession sqlSession = this.sqlSessionFactory.openSession();) { //执行业务操作,如:增删改查 } ``` #### 新增操作 需求:传入`UserModel`对象,然后将这个对象的数据插入到`t_user`表中。 ##### 创建类UserModel 新建一个`com.javacode2018.chat02.UserModel`类,代码如下: ```java 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元素`定义插入操作: ```xml <!-- 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`方法: ```java int insert(String statement, Object parameter) ``` 这个方法有2个参数: **statement**:表示那个操作,值为Mapper xml的`namespace.具体操作的id`,如需要调用`UserMapper.xml`中的`insertUser`操作,这个值就是: ```java com.javacode2018.chat02.UserMapper.insertUser ``` **parameter**:insert操作的参数,和Mapper xml中的insert中的`parameterType`指定的类型一致。 返回值为插入的行数。 `UserTest`类中新增一个测试用例: ```java @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(); } } ``` 运行输出如下: ```java 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中看一下,如下,插入成功: ```java 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,内部事务是非自动提交的方式,所以需要我们手动提交: ```java sqlSession.commit(); ``` 如果想自动提交事务,可以将上面的测试用例改成下面这样: ```java @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定义更新操作: ```xml <!-- 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`方法: ```java int update(String statement, Object parameter) ``` 这个方法有2个参数: **statement**:表示哪个操作,值为Mapper xml的`namespace.具体操作的id`,如需要调用`UserMapper.xml`中的`updateUser`操作,这个值就是: ```java com.javacode2018.chat02.UserMapper.updateUser ``` **parameter**:update操作的参数,和Mapper xml中的update中的`parameterType`指定的类型一致。 返回值为update影响行数。 `UserTest`类中新增一个测试用例: ```java @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); } } ``` 运行输出: ```java 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中去看一下: ```java 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元素定义删除操作: ```xml <!-- 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`方法: ```java int delete(String statement, Object parameter) ``` 这个方法有2个参数: **statement**:表示哪个操作,值为Mapper xml的`namespace.具体操作的id`,如需要调用`UserMapper.xml`中的`deleteUser`操作,这个值就是: ```java com.javacode2018.chat02.UserMapper. ``` **parameter**:delete操作的参数,和Mapper xml中的delete中的`parameterType`指定的类型一致。 返回值为delete影响行数。 `UserTest`类中新增一个测试用例: ```java @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); } } ``` 运行输出: ```java 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操作 ```xml <!-- 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添加一个用例: ```java @Test public void getUserList() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { //执行查询操作 List<UserModel> userModelList = sqlSession.selectList("com.javacode2018.chat02.UserMapper.getUserList"); log.info("结果:{}", userModelList); } } ``` 多插入几行,然后运行上面的用例,输出如下: ```java 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); ``` 这些方法的特点我们来看一下: 1. 调用这些方法,需要明确知道`statement`的值,statement的值为`namespace.具体操作的id`,这些需要打开`Mapper xml`中去查看了才知道,写起来不方便 2. parameter参数都是Object类型的,我们根本不知道这个操作具体类型是什么,需要查看`Mapper xml`才知道,随便传递个值,可能类型不匹配,但是只有在运行的时候才知道有问题 3. selectList方法返回的是一个泛型类型的,通过这个方法我们根本不知道返回的结果的具体类型,也需要去查看`Mapper xml`才知道 以上这几点使用都不是太方便,有什么方法能解决上面这些问题么? 有,这就是mybatis中的Mapper接口,我们可以定义一个interface,然后和Mapper xml关联起来,Mapper xml中的操作和Mapper接口中的方法会进行绑定,当我们调用Mapper接口的方法的时候,会间接调用到Mapper xml中的操作,接口的完整类名需要和Mapper xml中的namespace一致。 #### Mapper接口的用法(三步) ##### 步骤1:定义Mapper接口 去看一下,UserMapper.xml中的namespace,是: ```xml <mapper namespace="com.javacode2018.chat02.UserMapper"> ``` 我们创建的接口完整的名称需要和上面的namespace的值一样,下面我们创建一个接口`com.javacode2018.chat02.UserMapper`,如下: ```java package com.javacode2018.chat02; /** * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活! */ public interface UserMapper { } ``` UserMapper.xml中有4个操作,我们需要在UserMapper接口中也定义4个操作,和UserMapper.xml的4个操作对应,如下: ```java 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接口对象,如下: ```java /** * 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接口对象: ```java UserMapper mapper = sqlSession.getMapper(UserMapper.class); ``` ##### 步骤3:调用Mapper接口的方法对db进行操作 如调用UserMapper接口的insert操作: ```java @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中创建一个测试类,代码如下: ```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接口使用时注意的几点 1. Mapper接口的完整类名必须和对应的Mapper xml中的namespace的值一致 2. Mapper接口中方法的名称需要和Mapper xml中具体操作的id值一致 3. Mapper接口中方法的参数、返回值可以不和Mapper xml中的一致 #### Mapper接口的原理 这个使用java中的动态代理实现的,mybatis启动的时候会加载全局配置文件`mybatis-config.xml`,然后解析这个文件中的`mapper元素指定的UserMapper.xml`,会根据`UserMapper.xml的namespace的值`创建这个接口的一个动态代理,具体可以去看一下mybatis的源码,主要使用java中的Proxy实现的,使用`java.lang.reflect.Proxy`类中的`newProxyInstance`方法,我们可以创建任意一个接口的一个代理对象: ```java public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) ``` 我们使用Proxy来模仿Mapper接口的实现: ```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.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`用例,输出如下: ```java 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接口代理对象使用的是下面这个类,大家可以去研究一下: ```java 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); } } ``` ### 案例代码 ```java 链接:https://pan.baidu.com/s/1vt-MAX3oJOu9gyxZAhKkbg 提取码:i8op ``` <a style="display:none" target="_blank" href="https://mp.weixin.qq.com/s/_S1DD2JADnXvpexxaBwLLg" style="color:red; font-size:20px; font-weight:bold">继续收门徒,亲手带,月薪 4W 以下的可以来找我</a> ## 最新资料 1. <a href="https://mp.weixin.qq.com/s?__biz=MzkzOTI3Nzc0Mg==&mid=2247484964&idx=2&sn=c81bce2f26015ee0f9632ddc6c67df03&scene=21#wechat_redirect" target="_blank">尚硅谷 Java 学科全套教程(总 207.77GB)</a> 2. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484192&idx=1&sn=505f2faaa4cc911f553850667749bcbb&scene=21#wechat_redirect" target="_blank">2021 最新版 Java 微服务学习线路图 + 视频</a> 3. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484573&idx=1&sn=7f3d83892186c16c57bc0b99f03f1ffd&scene=21#wechat_redirect" target="_blank">阿里技术大佬整理的《Spring 学习笔记.pdf》</a> 4. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484544&idx=2&sn=c1dfe907cfaa5b9ae8e66fc247ccbe84&scene=21#wechat_redirect" target="_blank">阿里大佬的《MySQL 学习笔记高清.pdf》</a> 5. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485167&idx=1&sn=48d75c8e93e748235a3547f34921dfb7&scene=21#wechat_redirect" target="_blank">2021 版 java 高并发常见面试题汇总.pdf</a> 6. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485664&idx=1&sn=435f9f515a8f881642820d7790ad20ce&scene=21#wechat_redirect" target="_blank">Idea 快捷键大全.pdf</a> ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/1/2883e86e-3eff-404a-8943-0066e5e2b454.png)
#custom-toc-container