一、什么是自增ID、UUID、雪花ID
1、自增ID
有序 ID、自增ID的主键是有序的增加的,每条记录都是放在一条记录的后面。
优点:有序的增加、查询速度快、写入效率高、节省磁盘的空间、易读、性能好
缺点:导入旧数据ID可能会重复,不适合分库分表
2、UUID
无序随机 ID,字符串类型,生成没有规律,唯一的 (不推荐使用,会导致性能低下,从中间插入数据数据会往后移,消耗性能)
优点:适用于分库分表、由Java代码生成,不需要查询就知道ID
缺点:数据量大的时候会消耗性能、生成位数较长
3、雪花ID
是由算法生成,它可以生成递增且有时间顺序的64位整数,适用于分布式项目
优点:性能好、有序、适用于分布式项目、由Java代码生成,不需要查询就知道ID、所有需要生成唯一标识的都可以使用雪花ID
缺点:生成位数较长,传递到前端接受,可能出现精度丢失,可以转换成字符串解决
二、使用 自增ID、UUID、雪花ID
1、自增 ID 的使用
1)新建一个 Maven 项目,导入需要的依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<!-- Mybatis 依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- mysql 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
<!-- Lombok 依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!-- junit 依赖:测试代码-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependencies>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.12</version>
</dependency>
</dependencies>
2)新建一张 User 表
create table userinfo(
user_id int primary key auto_increment, -- 用户Id
user_name varchar(20), -- 用户名称
user_tel varchar(11) -- 用户电话
);
insert into userinfo(user_name, user_tel) value ('user1','12323451234'),('user2','12423454567');
select * from userinfo;
3)新建一个 User 实体类
@Data
public class User {
/**
* 定义实体时,基本类型选用包装类
* 如果不使用包装类,初始化对象后会有一个默认值
*/
private Integer uid;
private String userName;
private String tel;
}
4)在资源文件 resources 下创建 mybatis.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">
<!-- 这个是 mybatis 的核心配置文件-->
<configuration>
<!-- typeAliases 用于配置实体类的别名 -->
<typeAliases>
<!-- 方式一:为每个实体类分别指定一个别名,使用时不区分大小写 -->
<!-- <typeAlias type="edu.nf.ch02.entity.User" alias="user"/>-->
<!-- 方式二:直接使用指定实体类的包,这样 mybatis 就会自动给这个包下所有的
实体类指定创建别名,别名就是类名,不包括包名,并且也是不区分大小写 -->
<package name="edu.nf.ch01.entity"/>
</typeAliases>
<!-- 配置环境,主要是配置数据源,指定连接的数据库,default 指定使用默认的数据库环境的 id -->
<environments default="mysql">
<!-- 配置 MySQL 数据源的环境,id 通常指定数据库名称 -->
<environment id="mysql">
<!-- 事务管理使用 JDBC 的事务 -->
<transactionManager type="JDBC"/>
<!-- type指定为 pool, 数据源连接使用 mybatis 自带的连接池
目的是可以让连接复用-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/psm?useUnicode=true&characterEncoding=utf-8&useSSL=false&timeZone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<!-- 还可以配置其他数据源的环境 -->
<!-- <environment id="sqlserver">-->
<!-- </environment>-->
</environments>
<!-- 指定 Mapper 映射配置文件 -->
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
5)在 uitl 包下,创建 MybatisUtil 类
/**
* @Date 2023-09-07
* @Author qiu
*/
public class MybatisUtils {
/**
* 声明 SqlSessionFactory, 用于创建 SqlSession 实例
*/
private static SqlSessionFactory sqlSessionFactory;
/**
* 在静态代码块中初始化 SqlSessionFactory 工厂
*/
static {
// 通过查找 mybatis 的核心配置文件,先创建一个用于读取 xml 文件的输入流
try {
InputStream is = Resources.getResourceAsStream("mybatis.xml");
// 将输入流传给创建的 SqlSessionFactoryBuilder 进行解析,并初始化
// 整个 sqlSessionFactory 工厂对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
throw new RuntimeException("Init mybatis error", e);
}
}
/**
* SqlSession 是操作数据库的核心对象,相当于封装了整个 Connection
* 不带参数的 openSession 方法,得到的 SqlSession 是需要我们手动提交事务的
*
* @return
*/
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
/**
* openSession 方法还可以传入一个 Boolean 类型的参数
* 如果设置为 true,创建出来的 SqlSession 就会自动创建提交事务
* false 一样是手动提交
*
* @param autoCommit
* @return
*/
public static SqlSession getSqlSession(boolean autoCommit) {
return sqlSessionFactory.openSession(autoCommit);
}
}
6)写 UserDao 接口的实现类
void saveUser(User user);
7)实现 UserDao 接口
@Override
public void saveUser(User user) {
// 创建 sqlSession
SqlSession sqlSession = MybatisUtiles.getSqlSession(true);
try{
sqlSession.getMapper(UserDao.class).saveUser(user);
} catch (RuntimeException e){
throw new RuntimeException(e);
} finally {
sqlSession.close();
}
}
8)在 resource 配置文件中新建一个文件夹 mapper ,在mapper里再创建一个 UserInfoMapper.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">
<!-- 指定 Dao 完整的类名 -->
<mapper namespace="edu.nf.ch02.dao.UserDao">
<!-- useGeneratedKeys 设置为 true 表示要获取自增长的 key,
并将取出来的 id 保存到实体的 uid 字段中 -->
<insert id="saveUser" parameterType="user" useGeneratedKeys="true" keyProperty="uid">
insert into userinfo(user_name, user_tel)
values (#{userName}, #{tel});
</insert>
</mapper>
注意:自增ID的使用就是在mybatis的配置文件中配置 useGeneratedKeys="true" ,并且把获取自增长的 key, 并将取出来的 id 保存到实体的 uid 字段中,这个 uid 就是 User 实体类中的 uid.
9)单元测试
@Test
public void testSaveUser(){
User user = new User();
// 已经开启了自增ID,就不需要设置id的值了
// user.setUid(1);
user.setUserName("User2");
user.setTel("12334567890");
new UserDaoImpl().saveUser(user);
System.out.println(user.getUid());
System.out.println(user);
}
结果
可以看到,我们的 id 自有序的递增的,因为我清空了表,之前是有数据的,所有是从37开始的。
2、UUID
1)新建一个工具类 UUIDUtiles
/**
* @Date 2023-09-11
* @Author qiu
*/
public class UUIDUtiles {
public static String createUUID(){
String uid = UUID.randomUUID().toString();
// 取出 UUID 的 “-”,替换为 “”
// 3bcdddfc-523d-457f-a1ca-7378e2c78301 原本生成的id是有 ’-‘的
return uid.replace("-","");
}
public static void main(String[] args) {
System.out.println(createUUID());
}
}
2)新建一张表
create table stu_info(
stu_id char(32),
stu_name varchar(20)
)
因为生成的ID是32位的且是字符串类型,所以需要重新建一张表,stu_id 的类型是char位长是 32
3)新建一个 Student 实体类
@Data
public class Student {
private String stuId;
private String stuName;
}
4) 定义 dao 接口的方法
void saveStu(Student student);
5)实现 dao 接口的方法
@Override
public void saveStu(Student student) {
// 注意:getSession 设置为 true 表示自动提交事务
SqlSession sqlSession = MybatisUtiles.getSqlSession(true);
try {
sqlSession.getMapper(StuDao.class).saveStu(student);
} catch (Exception e){
throw new RuntimeException(e);
} finally {
sqlSession.close();
}
}
6)编写配置文件
<?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="edu.nf.ch02.dao.StuDao">
<insert id="saveStu" parameterType="edu.nf.ch02.entity.Student">
insert into stu_info(stu_id,stu_name) values(#{stuId},#{stuName});
</insert>
</mapper>
注意:因为是使用Java代码生成的id,所以不需要设置useGeneratedKeys="true" keyProperty="uid"
7)单元测试
@Test
public void testSaveStu(){
Student student = new Student();
student.setStuId(UUIDUtiles.createUUID());
student.setStuName("li");
new StuDaoImpl().saveStu(student);
System.out.println(student.getStuId());
}
结果
可以看到每一个生成的 ID 都是不一样的,大家可以执行多次查看结果
3、雪花 ID
1)新建一个 SnowFlakeUtiles 工具类
/**
* @Date 2023-09-11
* @Author qiu
*/
public class SnowFlakeUtiles {
public static long create() {
//生成的是不带-的字符串,类似于:b17f24ff026d40949c85a24f4f375d42
return IdUtil.getSnowflakeNextId();
}
public static void main(String[] args) {
System.out.println(create());
}
}
2)单元测试(使用上面的方法和表)
@Test
public void testSaveStu(){
Student student = new Student();
student.setStuId(String.valueOf(SnowFlakeUtiles.create()));
student.setStuName("li2");
new StuDaoImpl().saveStu(student);
System.out.println(student.getStuId());
}
结果
可以看到前面几位数字都是一样的,体现它的有序性,是不是比UUID更加易读易懂
三、结语
当数据非常大的时候会进行分库分表, 此时自增的 id 会带来业务上的问题(不同的表会产生相同的 id 值) 因此可以考虑使用雪花 id,生成雪花 id 可以使用开源的工具类生成,(例如:hutool) 雪花 id 是一个长度 64 位的 long 类型的 id。如果是小项目可以考虑使用自增id,中大型以上的项目就可以使用 UUID 和 雪花ID。
项目完整的案例我上传到了 gitee上地址:
https://gitee.com/qiu-feng1/mybatis-study.git