【Mybatis】【JDBC】MyBatis、Mybatis-Plus 、JDBC 关于自增 ID 的获取过程
1 前言
最近项目上线,比较忙,没怎么写哈,一个月多了终于忙的差不多了。今儿看一个关于自增 ID 获取过程。
我们平时插入数据,关于主键的值,我们从程序的角度看的话,大概分两种一种是主动,一种就是被动。
主动:即我们程序在插入的时候直接主动赋予主键 id 的值,比如 uuid、雪花算法生成的id等,也就是站在程序的角度是直接主动赋值。
被动:即通过数据库自增或者某个序列化器等数据库服务帮我们生成的主键值。
主动方式因为本身主键值已经有了,也方便我们继续进行后续的业务逻辑,那么针对这种被动自增的这种,我们需要获取到主键值,如何获取的呢?传统的 JDBC 以及 持久层框架类似 Mybatis/Mybatis-Plus 的获取方式一样么?我们本节就从这三种方式分别来看看。
2 准备工作
我们首先创建一张自增主键的表:
-- 创建序列 CREATE SEQUENCE my_test_id_ser START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; -- 创建表 CREATE TABLE "public"."my_test" ( "id" int8 NOT NULL DEFAULT nextval('my_test_id_ser'::regclass), "name" varchar(6) COLLATE "pg_catalog"."default", "password" varchar(64) COLLATE "pg_catalog"."default", "flag" bool, "number" int4, "weight" numeric(20,4), "create_time" timestamp(6), "remark" varchar(64) COLLATE "pg_catalog"."default", CONSTRAINT "my_test_pkey" PRIMARY KEY ("id") ) ;
@Data @ToStringpublic class MyTestPo implements Serializable{ private static final long serialVersionUID = 1L; private Long id; /** * name varchar */ private String name; /** * 密码 varchar */ private String password; /** * flag */ private Boolean flag; /** * number */ private Integer number; /** * weight */ private BigDecimal weight; /** * createTime */ private LocalDateTime createTime; /** * remark */ private String remark; }
3 执行分析
3.1 JDBC 方式
我这里就直接贴代码了哈:
package com.virtuous.demo.laboratory.jdbc; import java.sql.*; /** * @author: kuku * @description */ public class JdbcMain { public static void main(String[] args) throws SQLException { // 获取连接 Connection connection = DriverManager.getConnection("jdbc:postgresql://localhost:5432/test", "postgres", "xxx"); // 获取语句 String sql = "INSERT INTO my_test (name) VALUES (?)"; PreparedStatement preparedStatement = connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); preparedStatement.setString(1, "name"); preparedStatement.executeUpdate(); // 获取自增id ResultSet resultSet = preparedStatement.getGeneratedKeys(); if (resultSet.next()) { long generatedId = resultSet.getLong(1); System.out.println("自增id: " + generatedId); } } }
我们看下执行效果:
可以看到自增 id 确实获取到了,其中一点最重要的就是在获取语句对象的时候 connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); 从名字上来看获取自生成的 key,其实就是我们的自增 ID。
整体的执行过程还是比较多的,我这里主要把几个关键的类串一下执行过程:
有一个关键点就是 PreparedStatement 对象的 wantsGeneratedKeysAlways 属性设置为 true(默认是 false),继而进行结果的收集,那么 Mybatis 是如何执行获取的呢,我们来看看。
3.2 Mybatis 方式
我这里也就直接贴代码了哈,有兴趣的可以自己执行一下:
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> <settings> <setting name="logImpl" value="STDOUT_LOGGING"></setting> </settings> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"> </transactionManager> <dataSource type="POOLED"> <property name="driver" value="org.postgresql.Driver"/> <property name="url" value="jdbc:postgresql://localhost:5432/test"/> <property name="username" value="postgres"/> <property name="password" value="xxx"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/MyTestMapper.xml"/> </mappers> </configuration>
mapper.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.virtuous.demo.mapper.MyTestMapper"> <insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> INSERT INTO my_test (name) VALUES (#{po.name}) </insert> </mapper>
mapper接口
@Mapper public interface MyTestMapper { int insertUser(@Param("po") MyTestPo po); }
main方法
public class MybatisMain { public static void main(String[] args) throws Exception { // 配置文件创建 io InputStream reader = Resources.getResourceAsStream("config.xml"); // 创建配置文件构建器 XMLConfigBuilder builder = new XMLConfigBuilder(reader, null, null); // 解析我们的配置文件生成 configuration 对象 Configuration configuration = builder.parse(); // 根据 configuration 创建我们的 SqlSessionFactory (sqlSession工厂) SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); // 有了工厂我们就可以拿会话了,会话可以看作就是连接,有了连接我们就能执行 sql SqlSession sqlSession = sqlSessionFactory.openSession(true); // 获取我们的 mapper接口类 MyTestMapper mapper = sqlSession.getMapper(MyTestMapper.class); MyTestPo po = new MyTestPo(); po.setName("小二"); mapper.insertUser(po); System.out.println(po); } }
我们看看执行结果:
那么我们看下 Mybatis 又是如何获取到值的,大概在三个地方:
(1)在解析阶段,首先我们的 XML,在解析的时候会发现属性 useGeneratedKeys="true" keyProperty="id",解析后生成的 MappedStatement 会记录这两个属性(详细的解析步骤大家可以看我以前的解析过程):
(2)在执行阶段,创建 Statement 的时候,会根据 mappedStatement 的属性设置对应的属性值:
(3)在执行阶段,最后根据自增标志获取结果集中的自增值
3.3 Mybatis-Plus 方式
Mybatis-Plus 是对 Mybatis 的一种增强,它的使用上有相对于有两点变化:
(1)首先就是我们的 Mapper 直接继承 BaseMapper,然后我们的Mapper 就有了增删改查的功能了
@Mapper public interface MyTestMapper extends BaseMapper<MyTestPo> { }
(2)其次,比如我们的表名、主键是不规则的,可以通过 @TableName、@TableId 等 MP 提供的注解来标识到我们的实体类上:
@Data @ToString @TableName("my_test") // 表名 public class MyTestPo implements Serializable{ // 主键 类型是自增 @TableId(type = IdType.AUTO) private Long id; .... }
看执行效果,也是能获取到我们的自增值:
那么它的原理和 Mybatis 有什么区别的?我大概先猜一下,应该是跟在 Mybatis 的解析阶段的方式不同,其余的应该都是一样的。比如我们先不执行,先看下启动后生成的 MappedStatement ,从哪看呢?就从它的 SqlSessionFactory 看,可以看到此时的 MappedStatement 已经解析到 KeyGenerator。
那么接下来我们就看看它的解析来源,以前看过 Mybatis 的解析过程,但是关于 Mybaits-Plus 的解析我还真没看过,所以一起来学习下,从哪看呢?就从 Mybatis-Plus 的自动装配看起:
就不一点点带着大家看了,我这里根据调试的过程串了一下:
可以看到最后其实就是根据你的 Mapper 接口是不是继承了 Mybatis-Plus 的 Mapper 顶层接口,然后根据你的 Mapper 中的方法以及 Mapper 接口中的PO信息,给你做填充 MappedStatement。
4 小结
好啦,那我们总结一下三种方式:
(1)JDBC:是通过设置Statement 的 PreparedStatement.RETURN_GENERATED_KEYS,继而根据 ResultSet 得到自增的主键值。
(2)Mybatis:最后的落点也是 JDBC,通过前置分析 XML语句信息,useGeneratedKeys="true" keyProperty="id" 得到 MappedStatement,然后在创建 Statement 的时候,根据分析的信息也是通过设置 Statement 的 PreparedStatement.RETURN_GENERATED_KEYS,然后根据ResultSet 得到自增的主键值的,关键控制点就是 MappedStatement 的信息。
(3)Mybatis-Plus:最后的落点是 Mybatis 继而也是 JDBC,相对于 Mybatis 的话,在解析的过程中,根据 Mapper是否继承 Mybatis-Plus 的 Mapper 接口(给你做一些基础增删改查的填充 MappedStatement)以及你的 PO 信息(看你的主键以及主键的类型)进行增强,继而根据 MappedStatement 的信息处理(也就跟 Mybatis 的一样了)。
本节就看到这里,有理解不对的地方欢迎指正。