Java充电社
专辑
博文
联系我
本人继续续收门徒,亲手指导
MyBatis教程
-> 增删改知识点汇总及主键获取3种方式详解
1、MyBatis未出世之前我们那些痛苦的经历
2、MyBatis入门,你确定mybatis你玩的很溜?
3、MyBatis使用详解(-)
4、MyBatis使用详解(2)
5、详解传参的几种方式、原理、源码解析
6、增删改知识点汇总及主键获取3种方式详解
7、种查询详解
8、自动映射,使用需谨慎
9、延迟加载、鉴别器、继承
10、动态SQL,这么多种你都会么?
11、类型处理器
12、掌握缓存为查询提速!
上一篇:详解传参的几种方式、原理、源码解析
下一篇:种查询详解
<div style="display:none"></div> ### 主要内容 - 建库建表 - mybatis增删改返回值说明及源码解析 - jdbc获取自增值的3种方式详解 - mybatis获取自增值的3种方式详解 ### 建库建表 ```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; ``` ### 增删改返回值说明 mybatis中对db执行增删改操作,不管是新增、删除、还是修改,最后都会去调用jdbc中对应的方法,要么是调用`java.sql.Statement`的`executeUpdate`的方法,要么是调用`java.sql.PreparedStatement`的`executeUpdate`方法,这2个类的方法名称都是`executeUpdate`,他们的参数可能不一样,但是他们的返回值都是int,说明增删改的返回值都是int类型的,表示影响的行数,比如插入成功1行返回结果就是1,删除了10行记录,返回就是10,更新了5行记录,返回的就是5。 那么我们通过Mybatis中的Mapper接口来对db增删改的时候,mybatis的返回值支持哪些类型呢? int类型那肯定是支持的,jdbc执行增删改默认返回int类型,那mybatis当然也支持这个类型。 但是mybatis的返回值比jdbc更强大,对于增删改还支持下面几种类型: ```java int Integer long Long boolean Boolean void ``` mapper的增删改方法返回值必须为上面的类型,mybatis内部将jdbc返回的int类型转换为上面列表中指定的类型,我们来看一下mybatis这块的源码,源码在下面的方法中: ```java org.apache.ibatis.binding.MapperMethod#rowCountResult ``` 我们来看一下这个方法的源码: ```java private Object rowCountResult(int rowCount) { final Object result; if (method.returnsVoid()) { result = null; } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) { result = rowCount; } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) { result = (long)rowCount; } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) { result = rowCount > 0; } else { throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType()); } return result; } ``` mybatis中会使用上面这个方法最后会对jdbc 增删改返回的int结果进行处理,处理为mapper接口中增删改方法返回值的类型。 **int、Integer、long、Long我们就不说了,主要说一下返回值是boolean、Boolean类型,如果影响的行数大于0了,将返回true。** 下面我们来创建一个工程感受一下增删改各种返回值。 ### 创建案例 整个mybatis系列的代码采用maven模块的方式管理的,可以在文章底部获取,本次我们还是在上一篇的`mybatis-series`中进行开发,在这个项目中新建一个模块`chat04`,模块坐标如下: ```xml <groupId>com.javacode2018</groupId> <artifactId>chat04</artifactId> <version>1.0-SNAPSHOT</version> ``` 下面我们通过mybatis快速来实现对t_user表增删改。 #### 创建UserModel类 `mybatis-series\chat04\src\main\java\com\javacode2018\chat04\demo1\model`目录创建`UserModel.java`,如下: ```java package com.javacode2018.chat04.demo1.model; 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; } ``` #### 创建UserMapper接口 `mybatis-series\chat04\src\main\java\com\javacode2018\chat04\demo1\mapper`目录创建`UserMapper.java`,如下: ```java package com.javacode2018.chat04.demo1.mapper; import com.javacode2018.chat04.demo1.model.UserModel; /** * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活! */ public interface UserMapper { /** * 插入用户信息,返回影响行数 * * @param model * @return */ int insertUser(UserModel model); /** * 更新用户信息,返回影响行数 * * @param model * @return */ long updateUser(UserModel model); /** * 根据用户id删除用户信息,返回删除是否成功 * * @param userId * @return */ boolean deleteUser(Long userId); } ``` > 注意上面3个操作的返回类型,我们体验一下`int、long、boolean`类型的返回值。 #### 创建UserMapper.xml文件 `mybatis-series\chat04\src\main\resources\demo1`目录创建,`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.chat04.demo1.mapper.UserMapper"> <insert id="insertUser" parameterType="com.javacode2018.chat04.demo1.model.UserModel"> <![CDATA[ INSERT INTO t_user (id,name,age,salary,sex) VALUES (#{id},#{name},#{age},#{salary},#{sex}) ]]> </insert> <update id="updateUser" parameterType="com.javacode2018.chat04.demo1.model.UserModel"> <![CDATA[ UPDATE t_user SET name = #{name},age = #{age},salary = #{salary},sex = #{sex} WHERE id = #{id} ]]> </update> <update id="deleteUser" parameterType="java.lang.Long"> <![CDATA[ DELETE FROM t_user WHERE id = #{id} ]]> </update> </mapper> ``` #### 创建属性配置文件 `mybatis-series\chat04\src\main\resources`目录中创建`jdbc.properties`,如下: ```java jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8 jdbc.username=root jdbc.password=root123 ``` #### 创建mybatis全局配置文件 `mybatis-series\chat04\src\main\resources\demo1`目录创建,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> <!-- 引入外部jdbc配置 --> <properties resource="jdbc.properties"/> <!-- 环境配置,可以配置多个环境 --> <environments default="demo4"> <environment id="demo4"> <!-- 事务管理器工厂配置 --> <transactionManager type="JDBC"/> <!-- 数据源工厂配置,使用工厂来创建数据源 --> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="demo1/UserMapper.xml" /> </mappers> </configuration> ``` #### 引入logback日志支持 `chat04\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> ``` #### 创建测试用例Demo1Test `mybatis-series\chat04\src\test\java\com\javacode2018\chat04`目录创建`Demo1Test.java`,如下: ```java package com.javacode2018.chat04; import com.javacode2018.chat04.demo1.mapper.UserMapper; import com.javacode2018.chat04.demo1.model.UserModel; 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; /** * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活! */ @Slf4j public class Demo1Test { private SqlSessionFactory sqlSessionFactory; @Before public void before() throws IOException { //指定mybatis全局配置文件 String resource = "demo1/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(1L).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(); //执行更新操作 long 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; //执行删除操作 boolean result = mapper.deleteUser(userId); log.info("第1次删除:id={},返回值:{}", userId, result); result = mapper.deleteUser(userId); log.info("第2次删除:id={},返回值:{}", userId, result); } } } ``` #### 项目结构如下图 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/77/a727f77d-9790-4400-8584-1b5ae57e6b41.png) 注意项目结构如下图,跑起来有问题的可以对照一下。 #### 运行测试用例 ##### 测试int类型返回值 运行`com.javacode2018.chat04.Demo1Test#insertUser`,插入一条用户信息,输出如下: ```java 16:35.821 [main] DEBUG c.j.c.d.mapper.UserMapper.insertUser - ==> Preparing: INSERT INTO t_user (id,name,age,salary,sex) VALUES (?,?,?,?,?) 16:35.858 [main] DEBUG c.j.c.d.mapper.UserMapper.insertUser - ==> Parameters: 1(Long), 路人甲Java(String), 30(Integer), 50000.0(Double), 1(Integer) 16:35.865 [main] DEBUG c.j.c.d.mapper.UserMapper.insertUser - <== Updates: 1 16:35.865 [main] INFO com.javacode2018.chat04.Demo1Test - 影响行数:1 ``` ##### 测试long类型返回值 运行`com.javacode2018.chat04.Demo1Test#updateUser`,通过用户id更新用户信息,输出如下: ```java 17:49.084 [main] DEBUG c.j.c.d.mapper.UserMapper.updateUser - ==> Preparing: UPDATE t_user SET name = ?,age = ?,salary = ?,sex = ? WHERE id = ? 17:49.127 [main] DEBUG c.j.c.d.mapper.UserMapper.updateUser - ==> Parameters: 路人甲Java,你好(String), 18(Integer), 5000.0(Double), 0(Integer), 1(Long) 17:49.135 [main] DEBUG c.j.c.d.mapper.UserMapper.updateUser - <== Updates: 1 17:49.135 [main] INFO com.javacode2018.chat04.Demo1Test - 影响行数:1 ``` ##### 测试boolean类型返回值 运行`com.javacode2018.chat04.Demo1Test#deleteUser`,根据用户id删除用户信息,删除2次,输出如下: ```java 20:37.745 [main] DEBUG c.j.c.d.mapper.UserMapper.deleteUser - ==> Preparing: DELETE FROM t_user WHERE id = ? 20:37.785 [main] DEBUG c.j.c.d.mapper.UserMapper.deleteUser - ==> Parameters: 1(Long) 20:37.790 [main] DEBUG c.j.c.d.mapper.UserMapper.deleteUser - <== Updates: 0 20:37.791 [main] INFO com.javacode2018.chat04.Demo1Test - 第1次删除:id=1,返回值:false 20:37.793 [main] DEBUG c.j.c.d.mapper.UserMapper.deleteUser - ==> Preparing: DELETE FROM t_user WHERE id = ? 20:37.794 [main] DEBUG c.j.c.d.mapper.UserMapper.deleteUser - ==> Parameters: 1(Long) 20:37.795 [main] DEBUG c.j.c.d.mapper.UserMapper.deleteUser - <== Updates: 0 20:37.795 [main] INFO com.javacode2018.chat04.Demo1Test - 第2次删除:id=1,返回值:false ``` > 第一次删除成功,再次删除数据已经不存在了,返回`false`。 ### jdbc获取主键的几种方式 上面的案例中`inserUser`会向`t_user`表插入数据,`t_user`表的`id`是自动增长的,插入数据的时候我们不指定id的值,看看插入成功之后`userModel`对象和db中插入的记录是什么样的。 `com.javacode2018.chat04.Demo1Test#insertUser`代码改成下面这样: ```java @Test public void insertUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); //创建UserModel对象 UserModel userModel = UserModel.builder().name("郭富城").age(30).salary(50000D).sex(1).build(); //执行插入操作 int insert = mapper.insertUser(userModel); log.info("影响行数:{}", insert); log.info("{}", userModel); } } ``` 执行一下,输出: ```java 36:10.673 [main] DEBUG c.j.c.d.mapper.UserMapper.insertUser - ==> Preparing: INSERT INTO t_user (id,name,age,salary,sex) VALUES (?,?,?,?,?) 36:10.715 [main] DEBUG c.j.c.d.mapper.UserMapper.insertUser - ==> Parameters: null, 郭富城(String), 30(Integer), 50000.0(Double), 1(Integer) 36:10.721 [main] DEBUG c.j.c.d.mapper.UserMapper.insertUser - <== Updates: 1 36:10.722 [main] INFO com.javacode2018.chat04.Demo1Test - 影响行数:1 36:10.723 [main] INFO com.javacode2018.chat04.Demo1Test - UserModel(id=null, name=郭富城, age=30, salary=50000.0, sex=1) ``` 输出中插入成功1行,最后一行日志中输出了`userModel`对象所有属性信息,id是`null`的,我们去db中看一下这条记录: ```java mysql> SELECT * FROM t_user; +----+-----------+-----+----------+-----+ | id | name | age | salary | sex | +----+-----------+-----+----------+-----+ | 2 | 郭富城 | 30 | 50000.00 | 1 | +----+-----------+-----+----------+-----+ 1 row in set (0.00 sec) ``` db中插入的这条`郭富城`的id是2,当我们没有指定id,或者指定的id为null的时候,mysql会自动生成id的值。 那么我们如何mysql中获取这个自动增长的值呢?我们先看看jdbc是如何实现的 #### 方式1:jdbc内置的方式 ##### 用法 jdbc的api中为我们提供了获取自动生成主键的值,具体看这个方法: ```java java.sql.Statement#getGeneratedKeys ``` 看一下这个方法的定义: ```java /** * Retrieves any auto-generated keys created as a result of executing this * <code>Statement</code> object. If this <code>Statement</code> object did * not generate any keys, an empty <code>ResultSet</code> * object is returned. * *<p><B>Note:</B>If the columns which represent the auto-generated keys were not specified, * the JDBC driver implementation will determine the columns which best represent the auto-generated keys. * * @return a <code>ResultSet</code> object containing the auto-generated key(s) * generated by the execution of this <code>Statement</code> object * @exception SQLException if a database access error occurs or * this method is called on a closed <code>Statement</code> * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method * @since 1.4 */ ResultSet getGeneratedKeys() throws SQLException; ``` 这个方法会返回一个结果集,从这个结果集中可以获取自增主键的值。 不过使用这个方法有个前提,执行sql的时候需要做一个设置。 如果是通过`java.sql.Statement`执行sql,需要调用下面这个方法: ```java int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException ``` 注意上面这个方法的第二个参数需要设置为`java.sql.Statement.RETURN_GENERATED_KEYS`,表示需要返回自增列的值。 不过多数情况下,我们会使用`java.sql.PreparedStatement`对象来执行sql,如果想获取自增值,创建这个对象需要设置第2个参数的值,如下: ```java PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ``` 然后我们就可以通过`getGeneratedKeys`返回的`ResultSet`对象获取自动增长的值了,如下: ```java ResultSet generatedKeys = preparedStatement.getGeneratedKeys(); if (generatedKeys!=null && generatedKeys.next()) { log.info("自增值为:{}", generatedKeys.getInt(1)); } ``` ##### 案例 `com.javacode2018.chat04.Demo1Test`中新增一个测试用例,如下代码: ```java private String jdbcDriver = "com.mysql.jdbc.Driver"; private String jdbcUrl = "jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8"; private String jdbcUserName = "root"; private String jdbcPassword = "root123"; @Test public void jdbcInsertUser1() throws Exception { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet generatedKeys = null; try { UserModel userModel = UserModel.builder().name("黎明").age(30).salary(50000D).sex(1).build(); //执行jdbc插入数据操作 Class.forName(jdbcDriver); connection = DriverManager.getConnection(jdbcUrl, jdbcUserName, jdbcPassword); //注意创建PreparedStatement的时候,使用prepareStatement方法的第二个参数需要指定Statement.RETURN_GENERATED_KEYS preparedStatement = connection.prepareStatement("INSERT INTO t_user (name,age,salary,sex) VALUES (?,?,?,?)", Statement.RETURN_GENERATED_KEYS); int parameterIndex = 1; preparedStatement.setString(parameterIndex++, userModel.getName()); preparedStatement.setInt(parameterIndex++, userModel.getAge()); preparedStatement.setDouble(parameterIndex++, userModel.getSalary()); preparedStatement.setInt(parameterIndex++, userModel.getSex()); int count = preparedStatement.executeUpdate(); log.info("影响行数:{}", count); //获取自增值 generatedKeys = preparedStatement.getGeneratedKeys(); if (generatedKeys != null && generatedKeys.next()) { log.info("自增值为:{}", generatedKeys.getInt(1)); } } finally { if (generatedKeys != null && generatedKeys.isClosed()) { generatedKeys.close(); } if (preparedStatement != null && preparedStatement.isClosed()) { preparedStatement.close(); } if (connection != null && connection.isClosed()) { connection.close(); } } } ``` 上面代码中我们插入了一条用户的信息,没有指定用户的id,执行输出: ```java 21:22.410 [main] INFO com.javacode2018.chat04.Demo1Test - 影响行数:1 21:22.414 [main] INFO com.javacode2018.chat04.Demo1Test - 自增值为:5 ``` 我们去db中看一下这个记录的id,如下,确实是5: ```java mysql> SELECT * FROM t_user; +----+--------+-----+----------+-----+ | id | name | age | salary | sex | +----+--------+-----+----------+-----+ | 5 | 黎明 | 30 | 50000.00 | 1 | +----+--------+-----+----------+-----+ 1 row in set (0.00 sec) ``` #### 方式2:插入之后查询获取 ##### 用法 mysql中插入一条数据之后,可以通过下面的sql获取最新插入记录的id的值: ```java SELECT LAST_INSERT_ID() ``` 那么我们可以在插入之后,立即使用当前连接发送上面这条sql去获取自增列的值就可以。 ##### 案例 创建测试用例`com.javacode2018.chat04.Demo1Test#jdbcInsertUser2`,代码如下: ```java @Test public void jdbcInsertUser2() throws Exception { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet rs = null; try { UserModel userModel = UserModel.builder().name("梁朝伟").age(30).salary(50000D).sex(1).build(); //执行jdbc插入数据操作 Class.forName(jdbcDriver); connection = DriverManager.getConnection(jdbcUrl, jdbcUserName, jdbcPassword); //注意创建PreparedStatement的时候,使用prepareStatement方法的第二个参数需要指定Statement.RETURN_GENERATED_KEYS preparedStatement = connection.prepareStatement("INSERT INTO t_user (name,age,salary,sex) VALUES (?,?,?,?)", Statement.RETURN_GENERATED_KEYS); int parameterIndex = 1; preparedStatement.setString(parameterIndex++, userModel.getName()); preparedStatement.setInt(parameterIndex++, userModel.getAge()); preparedStatement.setDouble(parameterIndex++, userModel.getSalary()); preparedStatement.setInt(parameterIndex++, userModel.getSex()); int count = preparedStatement.executeUpdate(); log.info("影响行数:{}", count); //通过查询获取自增值 rs = connection.prepareStatement("SELECT LAST_INSERT_ID()").executeQuery(); if (rs != null && rs.next()) { log.info("自增值为:{}", rs.getInt(1)); } } finally { if (rs != null && rs.isClosed()) { rs.close(); } if (preparedStatement != null && preparedStatement.isClosed()) { preparedStatement.close(); } if (connection != null && connection.isClosed()) { connection.close(); } } } ``` 运行输出: ```java 26:55.407 [main] INFO com.javacode2018.chat04.Demo1Test - 影响行数:1 26:55.414 [main] INFO com.javacode2018.chat04.Demo1Test - 自增值为:6 ``` db中我们去看一下,`梁朝伟`的id是6,如下: ```java mysql> SELECT * FROM t_user; +----+-----------+-----+----------+-----+ | id | name | age | salary | sex | +----+-----------+-----+----------+-----+ | 5 | 黎明 | 30 | 50000.00 | 1 | | 6 | 梁朝伟 | 30 | 50000.00 | 1 | +----+-----------+-----+----------+-----+ 2 rows in set (0.00 sec) ``` #### 方式3:插入之前获取 oracle不知道大家有没有玩过,oracle中没有mysql中自动增长列,但是oracle有个功能可以实现自动增长,这个功能就是`序列`,序列就相当于一个自增器一样,有个初始值,每次递增的步长,当然这个序列提供了一些功能给我们使用,可以获取序列的当前值、下一个值,使用方式如下: ```java 1.先定义一个序列 2.获取下一个值:SELECT 序列名.NEXTVAL FROM dual; ``` 这个案例我只说一下具体步骤,代码就不写了,步骤: ```java 1.通过jdbc执行`SELECT 序列名.NEXTVAL FROM dual`获取序列的下一个值,如nextId 2.在代码中使用nextId的值 ``` 上面就是jdbc获取值增值的几种方式,jdbc中的这3中方式,mybatis中都提供了对应的 支持,下面我们来看mybatis中是如何实现的。 ### mybatis获取主键的3种方式 #### 方式1:内部使用jdbc内置的方式 ##### 用法 mybatis这个方式内部采用的是上面说的jdbc内置的方式。 我们需要在Mapper xml中进行配置,如: ```java <insert id="insertUser1" parameterType="com.javacode2018.chat04.demo1.model.UserModel" useGeneratedKeys="true" keyProperty="id"> <![CDATA[ INSERT INTO t_user (name,age,salary,sex) VALUES (#{name},#{age},#{salary},#{sex}) ]]> </insert> ``` 有2个关键参数必须要设置: - useGeneratedKeys:设置为true - keyProperty:参数对象中的属性名称,最后插入成功之后,mybatis会通过反射将自增值设置给keyProperty指定的这个属性 ##### 案例 `mybatis-series\chat04\src\main\resources\demo1\UserMapper.xml`中新增代码: ```xml <!-- 插入的时候获取值增值,必须需指定2个属性 useGeneratedKeys:设置为true keyProperty:参数对象中的属性,插入成功之后会将值增值设置给这个属性 --> <insert id="insertUser1" parameterType="com.javacode2018.chat04.demo1.model.UserModel" useGeneratedKeys="true" keyProperty="id"> <![CDATA[ INSERT INTO t_user (name,age,salary,sex) VALUES (#{name},#{age},#{salary},#{sex}) ]]> </insert> ``` Mapper接口中也新增代码,`com.javacode2018.chat04.demo1.mapper.UserMapper`中新增一个方法,如下: ```java int insertUser1(UserModel userModel); ``` 创建测试用例方法`com.javacode2018.chat04.Demo1Test#insertUser1`,如下: ```java @Test public void insertUser1() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); //创建UserModel对象 UserModel userModel = UserModel.builder().name("陈宝国").age(30).salary(50000D).sex(1).build(); //执行插入操作 int insert = mapper.insertUser1(userModel); log.info("影响行数:{}", insert); log.info("{}", userModel); } } ``` 注意上面的`userModel`对象,id没有设置值,运行输出: ```java 59:44.412 [main] DEBUG c.j.c.d.m.UserMapper.insertUser1 - ==> Preparing: INSERT INTO t_user (name,age,salary,sex) VALUES (?,?,?,?) 59:44.444 [main] DEBUG c.j.c.d.m.UserMapper.insertUser1 - ==> Parameters: 陈宝国(String), 30(Integer), 50000.0(Double), 1(Integer) 59:44.451 [main] DEBUG c.j.c.d.m.UserMapper.insertUser1 - <== Updates: 1 59:44.453 [main] INFO com.javacode2018.chat04.Demo1Test - 影响行数:1 59:44.455 [main] INFO com.javacode2018.chat04.Demo1Test - UserModel(id=8, name=陈宝国, age=30, salary=50000.0, sex=1) ``` 看上面最后一行输出,id的值为8,去db中看一下,如下: ```java mysql> SELECT * FROM t_user; +----+-----------+-----+----------+-----+ | id | name | age | salary | sex | +----+-----------+-----+----------+-----+ | 5 | 黎明 | 30 | 50000.00 | 1 | | 6 | 梁朝伟 | 30 | 50000.00 | 1 | | 7 | 陈宝国 | 30 | 50000.00 | 1 | | 8 | 陈宝国 | 30 | 50000.00 | 1 | +----+-----------+-----+----------+-----+ 4 rows in set (0.00 sec) ``` #### 方式2:插入后查询获取主键 ##### 用法 这个方式和上面介绍的jdbc的第二种方式一样,插入之后通过查询获取主键的值然后填充给指定的属性,mapper xml配置如下: ```xml <insert id="insertUser2" parameterType="com.javacode2018.chat04.demo1.model.UserModel"> <selectKey keyProperty="id" order="AFTER" resultType="long"> <![CDATA[ SELECT LAST_INSERT_ID() ]]> </selectKey> <![CDATA[ INSERT INTO t_user (name,age,salary,sex) VALUES (#{name},#{age},#{salary},#{sex}) ]]> </insert> ``` 关键代码是`selectKey`元素包含的部分,这个元素内部可以包含一个sql,这个sql可以在插入之前或者插入之后运行(之前还是之后通过order属性配置),然后会将sql运行的结果设置给keyProperty指定的属性,`selectKey`元素有3个属性需要指定: - keyProperty:参数对象中的属性名称,最后插入成功之后,mybatis会通过反射将自增值设置给keyProperty指定的这个属性 - order:指定selectKey元素中的sql是在插入之前运行还是插入之后运行,可选值(BEFORE|AFTER),这种方式中我们选择`AFTER` - resultType:keyProperty指定的属性对应的类型,如上面的id对应的类型是`java.lang.Long`,我们直接写的是别名`long` ##### 案例 `mybatis-series\chat04\src\main\resources\demo1\UserMapper.xml`中新增代码: ```xml <insert id="insertUser2" parameterType="com.javacode2018.chat04.demo1.model.UserModel"> <selectKey keyProperty="id" order="AFTER" resultType="long"> <![CDATA[ SELECT LAST_INSERT_ID() ]]> </selectKey> <![CDATA[ INSERT INTO t_user (name,age,salary,sex) VALUES (#{name},#{age},#{salary},#{sex}) ]]> </insert> ``` Mapper接口中也新增代码,`com.javacode2018.chat04.demo1.mapper.UserMapper`中新增一个方法,如下: ```java int insertUser2(UserModel userModel); ``` 创建测试用例方法`com.javacode2018.chat04.Demo1Test#insertUser2`,如下: ```java @Test public void insertUser2() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); //创建UserModel对象 UserModel userModel = UserModel.builder().name("周润发").age(30).salary(50000D).sex(1).build(); //执行插入操作 int insert = mapper.insertUser2(userModel); log.info("影响行数:{}", insert); log.info("{}", userModel); } } ``` 注意上面的`userModel`对象,id没有设置值,运行输出: ```java 22:18.140 [main] DEBUG c.j.c.d.m.UserMapper.insertUser2 - ==> Preparing: INSERT INTO t_user (name,age,salary,sex) VALUES (?,?,?,?) 22:18.173 [main] DEBUG c.j.c.d.m.UserMapper.insertUser2 - ==> Parameters: 周润发(String), 30(Integer), 50000.0(Double), 1(Integer) 22:18.180 [main] DEBUG c.j.c.d.m.UserMapper.insertUser2 - <== Updates: 1 22:18.183 [main] DEBUG c.j.c.d.m.U.insertUser2!selectKey - ==> Preparing: SELECT LAST_INSERT_ID() 22:18.183 [main] DEBUG c.j.c.d.m.U.insertUser2!selectKey - ==> Parameters: 22:18.197 [main] DEBUG c.j.c.d.m.U.insertUser2!selectKey - <== Total: 1 22:18.198 [main] INFO com.javacode2018.chat04.Demo1Test - 影响行数:1 22:18.200 [main] INFO com.javacode2018.chat04.Demo1Test - UserModel(id=11, name=周润发, age=30, salary=50000.0, sex=1) ``` 上面输出中执行了2条sql,先执行的插入,然后执行了一个查询获取自增值id,最后一行输出的id为11. 去db中看一下,如下: ```java mysql> SELECT * FROM t_user order by id desc limit 1; +----+-----------+-----+----------+-----+ | id | name | age | salary | sex | +----+-----------+-----+----------+-----+ | 11 | 周润发 | 30 | 50000.00 | 1 | +----+-----------+-----+----------+-----+ 1 row in set (0.00 sec) ``` #### 方式2:插入前查询获取主键 ##### 用法 这个方式和上面介绍的jdbc的第3种方式一样,会在插入之前先通过一个查询获取主键的值然后填充给指定的属性,然后在执行插入,mapper xml配置如下: ```xml <insert id="insertUser3" parameterType="com.javacode2018.chat04.demo1.model.UserModel"> <selectKey keyProperty="id" order="BEFORE" resultType="long"> <![CDATA[ 获取主键的select语句 ]]> </selectKey> <![CDATA[ INSERT INTO t_user (name,age,salary,sex) VALUES (#{name},#{age},#{salary},#{sex}) ]]> </insert> ``` 关键代码是`selectKey`元素包含的部分,这个元素内部可以包含一个sql,这个sql可以在插入之前或者插入之后运行(之前还是之后通过order属性配置),然后会将sql运行的结果设置给keyProperty指定的属性,`selectKey`元素有3个属性需要指定: - keyProperty:参数对象中的属性名称,最后插入成功之后,mybatis会通过反射将自增值设置给keyProperty指定的这个属性 - order:指定selectKey元素中的sql是在插入之前运行还是插入之后运行,可选值(BEFORE|AFTER),这种方式中我们选择`BEFORE` - resultType:keyProperty指定的属性对应的类型,如上面的id对应的类型是`java.lang.Long`,我们直接写的是别名`long` ##### 案例 这个案例我就不写了,大家可以拿oracle的序列去练习一下这个案例。 #### 源码 mybatis处理自动生产主键值的代码,主要看下面这个接口: ```java org.apache.ibatis.executor.keygen.KeyGenerator ``` 看一下这个接口的定义: ```java public interface KeyGenerator { void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter); void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter); } ``` 有2个方法,根据方法名称就可以知道,一个是插入sql执行之前调用的,一个是之后调用的,通过这2个方法mybatis完成了获取主键的功能。 这个接口默认有3个实现类: ```java org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator org.apache.ibatis.executor.keygen.SelectKeyGenerator org.apache.ibatis.executor.keygen.NoKeyGenerator ``` mybatis中获取主键的第一种方式就是在`Jdbc3KeyGenerator`类中实现的,其他2种方式是在第2个类中实现的,大家可以去看一下代码,设置断点感受一下,第3个类2个方法是空实现。 ### 案例代码 ```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