🧃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>

浙公网安备 33010602011771号