🧃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;
  1. create or replace trigger SEQ_STUDENT_ID:这部分指定了要创建的触发器的名称为 "SEQ_STUDENT_ID",如果同名的触发器已经存在,则会被替换。

  2. before insert on STUDENT for each row:这部分指定触发器的事件类型,即在每次往 "STUDENT" 表插入新行之前触发。

  3. begin:此关键字表示接下来是触发器的主体部分,这是一个代码块。

  4. select SEQ_STUDENT_ID.nextval into :new.id from dual;:这部分是触发器的实际操作。它使用 select 语句从名为 "SEQ_STUDENT_ID" 的序列中获取下一个值,并将该值赋给触发器关联的插入行的 "id" 字段(使用 :new.id 来引用插入行)。

  5. 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>
View Code

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();
    }
}
View Code


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>

 

posted @ 2023-09-03 19:37  Java小白的搬砖路  阅读(4658)  评论(0编辑  收藏  举报