通用mapper主键策略

首先主键策略和数据库关系很大,有些数据库支持主键自增,而有些数据库只能通过序列来获得。

新增的@KeySql 注解用于替换 @GeneratedValue 注解,因此 @KeySql 能以更简单方式实现原来的功能,下面的示例都先使用 @KeySql 进行配置,然后在使用 @GeneratedValue,大家可以自己选择。

JDBC 支持通过 getGeneratedKeys 方法取回主键的情况

这种情况首先需要数据库支持自增,其次数据库提供的 JDBC 支持 getGeneratedKeys 方法。

常见的如 MySql,SqlServer 支持这种模式。

这种情况下,配置主键策略最简单。

用法如下:

@Id
@KeySql(useGeneratedKeys = true)
private Long id;

或:

@Id
@GeneratedValue(generator = "JDBC")
private Long id;

为了让大家容易理解这里配置和 MyBatis 写法的关系,大家可以看看对应生成的 XML 代码:

<insert id="insert" useGeneratedKeys="true" keyProperty="id">
    insert into country (id, countryname, countrycode)
    values (#{id},#{countryname},#{countrycode})
</insert>

 注意:SqlServer 中使用时,需要设置 id 的 insertable=false

支持自增的数据库列表如下:

DB2VALUES IDENTITY_VAL_LOCAL()
MYSQLSELECT LAST_INSERT_ID()
SQLSERVERSELECT SCOPE_IDENTITY()
CLOUDSCAPEVALUES IDENTITY_VAL_LOCAL()
DERBYIDENTITY_VAL_LOCAL()
HSQLDBCALL IDENTITY()
SYBASESELECT @@IDENTITY
DB2_MFSELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1
INFORMIXselect dbinfo('sqlca.sqlerrd1') from systables where tabid=1

这类数据库主键策略配置示例如下:

@Id
//DEFAULT 需要配合 IDENTITY 参数(ORDER默认AFTER)
@KeySql(dialect = IdentityDialect.DEFAULT)
private Integer id;
//建议直接指定数据库
@Id
@KeySql(dialect = IdentityDialect.MYSQL)
private Integer id;

或:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

@GeneratedValue 用法还要配合 IDENTITY 参数(以及 ORDER 参数),这个参数值需要配置为对应的数据库,就是上面列表中的名字

mapper.identity=mysql
mapper.order=AFTER
//或者
#mapper.before=false

这种配置对应的 XML 形式为:

<insert id="insertAuthor">
    <selectKey keyProperty="id" resultType="int" order="AFTER">
      SELECT LAST_INSERT_ID()
    </selectKey>
    insert into country (id, countryname, countrycode)
    values (#{id},#{countryname},#{countrycode})
</insert>

IDENTITY 参数以及 ORDER 参数会决定 selectKey 中的 SQL 和 order 属性的值(这里是 AFTER)。

通过序列和任意 SQL 获取主键值

像 Oracle 中通过序列获取主键就属于这种情况,除了类似序列获取值外,还可以是获取 UUID 的 SQL 语句,例如 select uuid()

mapper.identity=select uuid()
#mapper.identity=select seq.nextval()
mapper.before=true

自增主键和非自增主键 的区别主要在于,自增主键 是插入表之后才有 id 的值,非自增主键是插入数据库前需要获取一个值作为主键。

在 Oracle 中,我们可以用下面的方式进行配置:

@Id
@KeySql(sql = "select SEQ_ID.nextval from dual", order = ORDER.BEFORE)
private Integer id;

或:

@Id
@GeneratedValue(
  strategy = GenerationType.IDENTITY,
  generator = "select SEQ_ID.nextval from dual")
private Integer id;

使用 @GeneratedValue 时也要配置一个 ORDER 全局参数

这种配置对应的 XML 代码如下:

<insert id="insertAuthor">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select SEQ_ID.nextval from dual
  </selectKey>
  insert into country (id, countryname, countrycode)
  values (#{id},#{countryname},#{countrycode})
</insert>

这种用法中,values 中必须出现主键的值,否则就插不到数据库。

除了 Oracle 这种外,还有一种更普遍的用法,就是使用 UUID。

例如下面的配置:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY,generator = "select uuid()")
private String id;

注意: SQL 返回值类型和这里接收的类型(String)一致。

CREATE DEFINER=`ip`@`%` FUNCTION `sys_guid`() RETURNS varchar(255) CHARSET utf8
BEGIN
    DECLARE uuid VARCHAR(32) DEFAULT '';
  SET uuid = replace(uuid(),'-','');
    RETURN uuid;
END

对于主键类型为varchar的,可以通过预定义的默认生成策略来生成主键(推荐)

    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private String id;

目前的生成策略为通过获得数据库生成的UUID来作为主键

mapper.identity=select sys_guid() from dual

同样对于varchar类型的主键,也可以直接使用系统生成的UUID,这个方式与GenerationType.IDENTITY的区别就是通过GenerationType.IDENTITY生成的主键能够在保存操作后,返回到实体中;而这种方式则不会

    @GeneratedValue("UUID")
    private String id;

@KeySql 介绍

上面的例子中都列举了 @KeySql 方式的用法。下面全面的看看这个注解:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface KeySql {
    /**
     * 是否使用 JDBC 方式获取主键,优先级最高,设置为 true 后,不对其他配置校验
     *
     * @return
     */
    boolean useGeneratedKeys() default false;
    /**
     * 优先级第二,根据配置的数据库类型取回主键,忽略其他配置
     *
     * @return
     */
    IdentityDialect dialect() default IdentityDialect.DEFAULT;
    /**
     * 取主键的 SQL
     *
     * @return
     */
    String sql() default "";
    /**
     * 和 sql 可以配合使用,默认使用全局配置中的 ORDER
     *
     * @return
     */
    ORDER order() default ORDER.DEFAULT;
}

通过上面的注释,大家可以看到主要的 3 个参数的优先级,useGeneratedKeys 优先级最高,其次是 dialect,最后是 sql。其中 order 只对 sql 方式有效。

useGeneratedKeysdialect 相当于预设的基本用法,和数据库以及驱动紧密相关。sqlorder 更灵活。

为了方便使用全局主键(例如:Vesta 是一款通用的ID产生器,互联网俗称统一发号器),通用 Mapper 4.0.2 版本增加了新的控制主键生成的策略。

@KeySql 注解增加了下面的方法:

/**
 * Java 方式生成主键,可以和发号器一类的服务配合使用
 *
 * @return
 */
Class<? extends GenId> genId() default GenId.NULL.class;

使用该功能的时候,需要配置 genId 属性。 由于生成主键的方式通常和使用环境有关,因此通用 Mapper 没有提供默认的实现。

GenId 接口如下:

public interface GenId<T> {
    class NULL implements GenId {
        @Override
        public Object genId(String table, String column) {
            throw new UnsupportedOperationException();
        }
    }

    T genId(String table, String column);
}

 通过接口方法可以看出,在生成 Id 时,可以得到当前的表名和列名,我们可以使用这两个信息生成和表字段相关的 Id 信息。也可以完全不使用这两个信息,生成全局的 Id。

使用 UUID 方式

首先我们需要提供一个能产生 UUID 的实现类:

public class UUIdGenId implements GenId<String> {
    @Override
    public String genId(String table, String column) {
        return UUID.randomUUID().toString();
    }
}

使用:

public class User {
    @Id
    @KeySql(genId = UUIdGenId.class)
    private String id;

}

 注意:我们只需要在注解中配置 @KeySql(genId = UUIdGenId.class) 即可,需要注意的是,如果你使用了 @KeySql 提供的其他方式,genId 就不会生效,genId 是所有方式中优先级最低的。

简单的全局时序ID

public class SimpleGenId implements GenId<Long> {
    private Long    time;
    private Integer seq;

    @Override
    public synchronized Long genId(String table, String column) {
        long current = System.currentTimeMillis();
        if (time == null || time != current) {
            time = current;
            seq = 1;
        } else if (current == time) {
            seq++;
        }
        return (time << 20) | seq;
    }
}

基于 Vesta 的实现 

public class VestaGenId implement GenId<Long> {
   public Long genId(String table, String column){
       //ApplicationUtil.getBean 需要自己实现
        //IdService 是Vesta中的接口
       IdService idService = ApplicationUtil.getBean(IdService.class);
       return idService.genId();
   }
}

@Component
@Lazy(false)
public class ApplicationContextRegister implements ApplicationContextAware {
    private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationContextRegister.class);
    private static ApplicationContext APPLICATION_CONTEXT;

    /**
     * 设置spring上下文  *  * @param applicationContext spring上下文  * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        LOGGER.debug("ApplicationContext registed-->{}", applicationContext);
        APPLICATION_CONTEXT = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return APPLICATION_CONTEXT;
    }
}

InsertListMapper 特殊支持

 通用 Mapper 提供了好几个 InsertListMapper 接口,最早提供的都是和数据库相关的方法,所以在 Id 策略上都有和数据库相关的限制。

最新增加的 tk.mybatis.mapper.additional.insert.InsertListMapper 接口是一个和数据库无关的方法,他不支持任何的主键策略。但是有了 genId 方式后,这个接口增加了对 @KeySql 注解 genId 方法的支持。

import tk.mybatis.mapper.additional.insert.InsertListMapper;

public interface UserMapper extends InsertListMapper<User> {

}


@Test
public void testInsertList() {
    SqlSession sqlSession = getSqlSession();
    try {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = new ArrayList<User>(countries.length);
        for (int i = 0; i < countries.length; i++) {
            userList.add(new User(countries[i][0], countries[i][1]));
        }
        Assert.assertEquals(countries.length, mapper.insertList(userList));
        for (User user : userList) {
            Assert.assertNotNull(user.getId());
            System.out.println(user.getId());
        }
    } finally {
        sqlSession.close();
    }
}

通过 genId 方式可以批量插入,并且可以回写 ID。

想要自己实现全局唯一id生成器可以参考:https://mp.csdn.net/console/editor/html/95203342

order="AFTER"  mysql 自增长ID    先保存的数据 再生成的ID   

order="BEFORE"  oracle  自增长  UUID  先生成ID 再保存数据

order="BEFORE"  mysql UUID  先生成ID 再保存数据
 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot可以很方便地整合通用Mapper,只需要在pom.xml中添加通用Mapper的依赖,然后在配置文件中配置数据源和通用Mapper的相关属性即可。 具体步骤如下: 1. 在pom.xml中添加通用Mapper的依赖: ```xml <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>2.1.5</version> </dependency> ``` 2. 在配置文件中配置数据源和通用Mapper的相关属性: ```yaml spring: datasource: url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root driver-class-name: com.mysql.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true mapper: mappers: - tk.mybatis.mapper.common.Mapper not-empty: false identity: MYSQL ``` 其中,mapper.mappers指定了要使用的Mapper接口,这里使用了通用MapperMapper接口;mapper.identity指定了主键生成策略,这里使用了MySQL的自增长主键。 3. 在Mapper接口中继承通用MapperMapper接口即可使用通用Mapper提供的方法: ```java public interface UserMapper extends Mapper<User> { } ``` 这样就可以使用通用Mapper提供的方法来进行数据库操作了,例如: ```java @Autowired private UserMapper userMapper; public void addUser(User user) { userMapper.insert(user); } public void updateUser(User user) { userMapper.updateByPrimaryKeySelective(user); } public void deleteUser(Long id) { userMapper.deleteByPrimaryKey(id); } public User getUser(Long id) { return userMapper.selectByPrimaryKey(id); } public List<User> getUsers() { return userMapper.selectAll(); } ``` 以上就是Spring Boot整合通用Mapper的基本步骤,希望能对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值