🧃MyBatisPlus操作Oracle(插入数据主键自增)
示例代码:https://gitee.com/zhang-zhixi/springboot-mp-oracle-auto.git
代码不需要修改,需要操作的是相对应的数据库,在Oracle中是不支持ID自增的,这时候我们就需要手动设置一些规则,来让ORM框架支持自增(实际是数据库层面做的)
如图所示,使用MP插入Oracle表数据,不做设置的话是插入不了自增ID的:
MyBatisPlus操作Oracle:触发器调用序列生成ID(oracle11g)
关于oracle 11g和12c发行时间以及区别:
Oracle 11g是Oracle公司在2007年发行的一款数据库软管理系统。而Oracle 12c则是在2013年发行的。 Oracle 11g和12c虽然都是Oracle公司的数据库管理系统,但在某些方面存在一些主要区别: Multitenant Architecture:Oracle 12c引入了新的多租户体系结构,允许多个独立的可插拔数据库(PDBs)运行在同一个容器数据库(CDB)中,而无需消耗额外的系统资源。而Oracle 11g没有这个功能。 数据优化:Oracle 12c引入了自动数据优化(ADO)功能,可根据数据的使用情况和热度,自动将数据移至优化的存储层,从而提高了整体性能和效率。而Oracle 11g并没有这项功能。 内存管理:Oracle 12c优化了内存管理,引入了自动大页特性,可以大幅度提高数据库的性能和可扩展性。而Oracle 11g对此没有明显改进。 安全性:Oracle 12c提供了更高级别的安全性,包括数据红利射(Data Redaction)、高级网络服务安全等,而Oracle 11g则没有这些新的安全特性。 性能诊断:Oracle 12c增强了对性能问题的诊断和解决能力,具备了提供实时数据的活动报告,包括活动会话历史(ASH)、自动工作负载存储库(AWR)等,而Oracle 11g的这些功能相对较弱。 兼容性:Oracle 12c拥有向下兼容性,可处理在Oracle 11g中运行的应用程序,而Oracle 11g则不能处理在Oracle 12c中运行的应用程序。
一、创建表
CREATE TABLE "STUDENT" ( "ID" NUMBER NOT NULL, "NAME" VARCHAR2(255 BYTE), "AGE" NUMBER(2,0), "SEX" NVARCHAR2(2), "TEL" NUMBER, "EMAIL" VARCHAR2(255 BYTE), "BIRTHDAY" DATE, "CREATE_TIME" DATE, "UPDATE_TIME" DATE, "IS_DELETED" NUMBER, CONSTRAINT "PK_STUDENT" PRIMARY KEY ("ID") ); COMMENT ON COLUMN "STUDENT"."ID" IS '主键'; COMMENT ON COLUMN "STUDENT"."NAME" IS '姓名'; COMMENT ON COLUMN "STUDENT"."AGE" IS '年龄'; COMMENT ON COLUMN "STUDENT"."SEX" IS '性别'; COMMENT ON COLUMN "STUDENT"."TEL" IS '电话号码'; COMMENT ON COLUMN "STUDENT"."EMAIL" IS '电子邮箱'; COMMENT ON COLUMN "STUDENT"."BIRTHDAY" IS '生日'; COMMENT ON COLUMN "STUDENT"."CREATE_TIME" IS '创建时间'; COMMENT ON COLUMN "STUDENT"."UPDATE_TIME" IS '更新时间'; COMMENT ON COLUMN "STUDENT"."IS_DELETED" IS '是否删除(0-未删除,1已删除)';
二、创建序列
Oracle 中的标识符(如表名、列名、序列名等)的长度限制为30个字符,通常的规则就是:SEQ_表名_ID
创建序列:
create sequence SEQ_STUDENT_ID minvalue 1 --最小值 nomaxvalue --不设置最大值(由机器决定),或 根据表字段的值范围设置 maxvalue start with 1 --从1开始计数,数值可变 increment by 1 --每次加1,数值可变 nocycle --nocycle:一直累加,不循环;cycle:达到最大值后,将从头开始累加 -- nocache:在内存中不缓存序列的值;cache在内存中缓存序列的值 nocache;
[生成序列ID]
-- 为序列生成一个新ID SELECT SEQ_STUDENT_ID.NEXTVAL FROM DUAL;
[查询序列]
-- 序列名、拥有者(用户)、增量、最小值、最大值等 -- 查询所有用户的序列 SELECT * FROM ALL_SEQUENCES; -- 查询当前用户序列 SELECT * FROM USER_SEQUENCES; -- 查询序列的值 SELECT "SEQ_STUDENT_ID"."CURRVAL" FROM dual;
[删除序列]
-- 删除序列 DROP SEQUENCE SEQ_STUDENT_ID
三、创建触发器
说明下:我在使用Navicat给ID设置默认值为序列时,在Oracle11g-11.2.0.1.0不能正常添加,会报错,在Oracle12c-12.2.0.1.0可以正常添加
原因是:
1、通过创建序列然后给ID设置DEFAULT 这种方式是Oracle 12c开始引入的新特性。在此之前,Oracle没有自增主键的概念。 在创建表的时候,字段可以直接使用序列的NEXTVAL为默认值,当INSERT时未指定ID,则由Oracle自动使用序列提供值。 这种方式操作简单,直接用序列的nextval作为默认值,逻辑更清晰。 2、创建序列,创建触发器 这是在Oracle 12c之前,我们实现自增长ID的一种较为常见的方式。 触发器的作用是在插入数据时,触发器会自动将序列的nextval值赋给ID,相当于模拟了其他数据库的自增长ID功能。 这种方式步骤较多,需要先创建序列,然后创建触发器,然后在插入数据之前调用触发器对ID进行自增。
此处两种方式我都会演示,使用序列或者使用触发器:
第一种:创建触发器(通用),此处演示版本为11g
create or replace trigger SEQ_STUDENT_ID before insert on STUDENT for each row begin select SEQ_STUDENT_ID.nextval into :new.id from dual; end;
-
create or replace trigger SEQ_STUDENT_ID
:这部分指定了要创建的触发器的名称为 "SEQ_STUDENT_ID",如果同名的触发器已经存在,则会被替换。 -
before insert on STUDENT for each row
:这部分指定触发器的事件类型,即在每次往 "STUDENT" 表插入新行之前触发。 -
begin
:此关键字表示接下来是触发器的主体部分,这是一个代码块。 -
select SEQ_STUDENT_ID.nextval into :new.id from dual;
:这部分是触发器的实际操作。它使用select
语句从名为 "SEQ_STUDENT_ID" 的序列中获取下一个值,并将该值赋给触发器关联的插入行的 "id" 字段(使用:new.id
来引用插入行)。 -
end;
:此关键字表示触发器主体的结束。
因此,这个触发器的目的是在每次往 "STUDENT" 表插入新行之前,从名为 "SEQ_STUDENT_ID" 的序列中获取下一个值,并将其赋给 "id" 字段。这样可以确保每个新插入的行都有唯一的 "id" 值。
删除触发器命令:DROP TRIGGER SEQ_STUDENT_ID;
第二种:给ID默认值设置为序列,不用单独创建触发器(对11g不生效),此处演示版本为12c
前提是需要先创建好序列,然后格式为:"用户名"."序列名"."NEXTVAL"
四、插入测试数据
示例代码有,自行测试即可
MyBatisPlus操作Oracle:使用MP默认的主键生成策略实现(依赖序列)
一、创建序列
create sequence SEQ_STUDENT_ID minvalue 1 --最小值 nomaxvalue --不设置最大值(由机器决定),或 根据表字段的值范围设置 maxvalue start with 1 --从1开始计数,数值可变 increment by 1 --每次加1,数值可变 nocycle --nocycle:一直累加,不循环;cycle:达到最大值后,将从头开始累加 -- nocache:在内存中不缓存序列的值;cache在内存中缓存序列的值 nocache;
二、添加Oracle主键生成策略支持(代码调用生成序列的操作)
@Configuration public class MybatisConfig { @Bean public OracleKeyGenerator oracleKeyGenerator() { return new OracleKeyGenerator(); } }
三、在实体类加注解,ID需指定为INPUT
package com.zhixi.pojo; import com.baomidou.mybatisplus.annotation.*; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Date; /** * @TableName STUDENT */ @Data @NoArgsConstructor @AllArgsConstructor @TableName(value = "STUDENT") @KeySequence(value = "SEQ_STUDENT_ID") public class Student implements Serializable { @TableField(exist = false) private static final long serialVersionUID = 1L; /** * 主键 */ @TableId(value = "ID", type = IdType.INPUT) private Long id; /** * 姓名 */ @TableField(value = "NAME") private String name; /** * 年龄 */ @TableField(value = "AGE") private Integer age; /** * 性别 */ @TableField(value = "SEX") private String sex; /** * 电话号码 */ @TableField(value = "TEL") private Long tel; /** * 电子邮箱 */ @TableField(value = "EMAIL") private String email; /** * 生日 */ @JsonFormat(pattern = "yyyy-MM-dd") @TableField(value = "BIRTHDAY") private Date birthday; /** * 创建时间 */ @JsonFormat(pattern = "yyyy-MM-dd") @TableField(value = "CREATE_TIME") private Date createTime; /** * 更新时间 */ @JsonFormat(pattern = "yyyy-MM-dd") @TableField(value = "UPDATE_TIME") private Date updateTime; /** * 是否删除(0-未删除,1已删除) */ @TableField(value = "IS_DELETED") private Long isDeleted; }
四、测试
@Autowired private StudentService studentService; @GetMapping("/insertStudent") public String insertStudent() { Student student = new Student(); student.setName("zhangsan"); student.setAge(23); student.setSex("男"); studentService.save(student); return "success"; }
MyBatisPlus操作Oracle:使用自定义的主键生成策略(不依赖序列)
1、自定义ID生成策略
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; /** * @author zhangzhixi * @version 1.0 * @description 自定义ID生成机制 * @date 2023-09-16 13:00 */ @Component @Slf4j public class CustomIdGenerator implements IdentifierGenerator { /** * 全局锁 */ private static final Object GLOBAL_LOCK = new Object(); @Autowired JdbcTemplate jdbcTemplate; @Override public Number nextId(Object entity) { Long newId; // 返回表最大id+1 try { TableName tableName = entity.getClass().getAnnotation(TableName.class); String sql = "select max(id) from " + tableName.value(); synchronized (GLOBAL_LOCK) { newId = jdbcTemplate.queryForObject(sql, Long.class); return newId == null ? 1L : newId + 1; } } catch (NullPointerException e) { throw new RuntimeException("获取表最大id失败,请在实体上添加@TableName注解"); } } }
1.1、自定义ID生成策略升级,使用Redis解决批量插入错误问题
pom:
<!--整合Redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--阿里巴巴JSON格式化转换--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.79</version> </dependency> <!--springboot2.x以后用得是lettuce:lettuce默认连接池使用 common-pool2 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
RedisConfig:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * @ClassName RedisConfig * @Author zhangzhixi * @Description Redis配置类 * @Date 2022-4-29 10:23 * @Version 1.0 */ @Configuration public class RedisConfig { /** * @param redisConnectionFactory:配置不同的客户端,这里注入的redis连接工厂不同: JedisConnectionFactory、LettuceConnectionFactory * @功能描述 :配置Redis序列化,原因如下: * (1) StringRedisTemplate的序列化方式为字符串序列化, * RedisTemplate的序列化方式默为jdk序列化(实现Serializable接口) * (2) RedisTemplate的jdk序列化方式在Redis的客户端中为乱码,不方便查看, * 因此一般修改RedisTemplate的序列化为方式为JSON方式【建议使用GenericJackson2JsonRedisSerializer】 */ @Bean(name = "redisTemplate") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = serializer(); RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); // key采用String的序列化方式 redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8); // value序列化方式采用jackson redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer); // hash的key也采用String的序列化方式 redisTemplate.setHashKeySerializer(StringRedisSerializer.UTF_8); //hash的value序列化方式采用jackson redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } /** * 此方法不能用@Ben注解,避免替换Spring容器中的同类型对象 */ public GenericJackson2JsonRedisSerializer serializer() { return new GenericJackson2JsonRedisSerializer(); } }
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; import java.util.Objects; /** * @author zhangzhixi * @version 1.0 * @description 自定义ID生成机制 * @date 2023-09-16 13:00 */ @Component @Slf4j public class CustomIdGenerator implements IdentifierGenerator { /** * 全局锁,用于保证线程安全 * TODO:后续可以考虑使用分布式锁 */ private static final Object GLOBAL_LOCK = new Object(); @Autowired JdbcTemplate jdbcTemplate; @Autowired private RedisTemplate<String, Object> redisTemplate; @Override public Number nextId(Object entity) { Long newId; try { TableName tableName = entity.getClass().getAnnotation(TableName.class); String sql = "select max(id) from " + tableName.value(); synchronized (GLOBAL_LOCK) { ValueOperations<String, Object> redisOpsValue = redisTemplate.opsForValue(); // 判断在redis中key是否存在:如果不存在,就从数据库中获取最大id,然后存入redis Object result = redisOpsValue.get(tableName.value()); if (Objects.isNull(result)) { newId = jdbcTemplate.queryForObject(sql, Long.class); redisOpsValue.set(tableName.value(), newId = newId == null ? 1 : newId + 1); return newId; } return redisOpsValue.increment(tableName.value(), 1); } } catch (NullPointerException e) { throw new RuntimeException("获取表最大id失败,请在实体上添加@TableName注解"); } } }
1.2、自定义ID生成策略升级,使用Hash数据结构替换1.1的String
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; /** * @author zhangzhixi * @version 1.0 * @description 自定义ID生成机制 * @date 2023-09-16 13:00 */ @Component @Slf4j public class CustomIdGenerator implements IdentifierGenerator { /** * 全局锁,用于保证线程安全 * TODO:后续可以考虑使用分布式锁 */ private static final Object GLOBAL_LOCK = new Object(); /** * 数据库ID缓存 */ public static final String CACHE_DB_IDS = "db_ids"; @Autowired JdbcTemplate jdbcTemplate; @Autowired private RedisTemplate<String, Object> redisTemplate; @Override public Number nextId(Object entity) { Long newId; try { TableName tableName = entity.getClass().getAnnotation(TableName.class); String sql = "select max(id) from " + tableName.value(); synchronized (GLOBAL_LOCK) { HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash(); // 判断在redis中key是否存在:如果不存在,就从数据库中获取最大id,然后存入redis Boolean isTableExists = hashOperations.hasKey(CACHE_DB_IDS, tableName.value()); if (!isTableExists) { newId = jdbcTemplate.queryForObject(sql, Long.class); hashOperations.put(CACHE_DB_IDS, tableName.value(), newId = newId == null ? 1 : newId + 1); return newId; } // 如果存在,就从redis中获取最大id,然后自增1 return hashOperations.increment(CACHE_DB_IDS, tableName.value(), 1); } } catch (NullPointerException e) { throw new RuntimeException("获取表最大id失败,请在实体上添加@TableName注解"); } } }
2、实体
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Date; /** * @TableName STUDENT */ @Data @NoArgsConstructor @AllArgsConstructor @TableName(value = "STUDENT") public class Student implements Serializable { @TableField(exist = false) private static final long serialVersionUID = 1L; /** * 主键:默认主键规则是雪花算法,since 3.3.0 * DefaultIdentifierGenerator(雪花算法) * 通过实现IdentifierGenerator接口,自定义主键生成策略 */ //@TableId(value = "ID", type = IdType.ASSIGN_ID) @TableId(value = "ID") private Long id; /** * 姓名 */ @TableField(value = "NAME") private String name; /** * 年龄 */ @TableField(value = "AGE") private Integer age; /** * 性别 */ @TableField(value = "SEX") private String sex; /** * 电话号码 */ @TableField(value = "TEL") private Long tel; /** * 电子邮箱 */ @TableField(value = "EMAIL") private String email; /** * 生日 */ @JsonFormat(pattern = "yyyy-MM-dd") @TableField(value = "BIRTHDAY") private Date birthday; /** * 创建时间 */ @JsonFormat(pattern = "yyyy-MM-dd") @TableField(value = "CREATE_TIME") private Date createTime; /** * 更新时间 */ @JsonFormat(pattern = "yyyy-MM-dd") @TableField(value = "UPDATE_TIME") private Date updateTime; /** * 是否删除(0-未删除,1已删除) */ @TableField(value = "IS_DELETED") private Long isDeleted; }
3、测试
@GetMapping("/insertStudent") public String insertStudent() { Student student = new Student(); student.setName("zhangsan"); student.setAge(23); student.setSex("男"); studentService.save(student); return "success"; }
4、end:使用Mybatis如何生成主键自增?
使用上面的是通过MP提供的主键生成策略,如果使用mybati,xml的方式如何插入数据呢?
可以通过selectKey标签来实现,如下,仅供参考
<insert id="saveUser"> <selectKey keyProperty="id" resultType="java.lang.Long" order="BEFORE"> select nvl(max(id),0)+1 from T_USER </selectKey> insert into T_USER <trim prefix="(" suffix=")" suffixOverrides=","> ID, <if test="name != null"> F_NAME, </if> <if test="age != null"> F_AGE, </if> <if test="address != null"> F_ADDRESS, </if> </trim> values <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> #{id}, </if> <if test="name != null"> #{name}, </if> <if test="age != null"> #{age}, </if> <if test="address != null"> #{address}, </if> </trim> </insert>