SpringBoot系列教程JPA之新增记录使用姿势上一篇文章介绍了如何快速的搭建一个JPA的项目环境,并给出了一个简单的演示demo,接下来我们开始业务教程,也就是我们常说的CURD,接下来进入第一
SpringBoot系列教程JPA之新增记录使用姿势
上一篇文章介绍了如何快速的搭建一个JPA的项目环境,并给出了一个简单的演示demo,接下来我们开始业务教程,也就是我们常说的CURD,接下来进入第一篇,如何添加数据
通过本篇文章,你可以get到以下技能点
- POJO对象如何与表关联
- 如何向DB中添加单条记录
- 如何批量向DB中添加记录
- save 与 saveAndFlush的区别
<!-- more -->
I. 环境准备
实际开始之前,需要先走一些必要的操作,如安装测试使用mysql,创建SpringBoot项目工程,设置好配置信息等,关于搭建项目的详情可以参考前一篇文章 190612-SpringBoot系列教程JPA之基础环境搭建
下面简单的看一下演示添加记录的过程中,需要的配置
1. 表准备
沿用前一篇的表,结构如下
CREATE TABLE `money` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名', `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `name` (`name`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
2. 项目配置
配置信息,与之前有一点点区别,我们新增了更详细的日志打印;本篇主要目标集中在添加记录的使用姿势,对于配置说明,后面单独进行说明
## DataSourcespring.datasource.url=jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=falsespring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.username=rootspring.datasource.password=## jpa相关配置spring.jpa.database=MYSQLspring.jpa.hibernate.ddl-auto=nonespring.jpa.show-sql=truespring.jackson.serialization.indent_output=truespring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
II. Insert使用教程
在开始之前,先声明一下,因为个人实际项目中并没有使用到JPA,对JPA的原则和hibernate的一些特性了解的也不多,目前处于学习探索阶段,主要是介绍下使用姿势,下面的东西都是经过测试得出,有些地方描述可能与规范不太一样,或者有些差错,请发现的大佬指正
接下来我们进入正题,如何通过JPA实现我们常见的Insert功能
1. POJO与表关联
首先第一步就是将POJO对象与表关联起来,这样就可以直接通过java的操作方式来实现数据库的操作了;
我们直接创建一个MoneyPo对象,包含上面表中的几个字段
@Datapublic class MoneyPO { private Integer id; private String name; private Long money; private Byte isDeleted; private Timestamp createAt; private Timestamp updateAt;}
自然而然地,我们就有几个问题了
- 这个POJO怎么告诉框架它是和表Money绑定的呢?
- Java中变量命令推荐驼峰结构,那么
isDeleted
又如何与表中的is_deleted
关联呢? - POJO中成员变量的类型如何与表中的保持一致呢,如果不一致会怎样呢?
针对上面的问题,一个一个来说明
对hibernate熟悉的同学,可能知道我可以通过xml配置的方式,来关联POJO与数据库表(当然mybatis也是这么玩的),友情链接一下hibernate的官方说明教程;我们使用SpringBoot,当然是选择注解的方式了,下面是通过注解的方式改造之后的DO对象
package com.git.hui.boot.jpa.entity;import lombok.Data;import org.springframework.data.annotation.CreatedDate;import javax.persistence.*;import java.sql.Timestamp;/** * Created by @author yihui in 21:01 19/6/10. */@Data@Entity(name="money")public class MoneyPO { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Integer id; @Column(name = "name") private String name; @Column(name = "money") private Long money; @Column(name = "is_deleted") private Byte isDeleted; @Column(name = "create_at") @CreatedDate private Timestamp createAt; @Column(name = "update_at") @CreatedDate private Timestamp updateAt;}
有几个有意思的地方,需要我们注意
a. entity注解
@Entity
这个注解比较重要,用于声明这个POJO是一个与数据库中叫做 money
的表关联的对象;
@Entity
注解有一个参数name,用于指定表名,如果不主动指定时,默认用类名,即上面如果不指定那么,那么默认与表moneypo
绑定
另外一个常见的方式是在类上添加注解 @Table
,然后指定表名,也是可以的
@Data@Entity@Table(name = "money")public class MoneyPO {}
b. 主键指定
我们可以看到id上面有三个注解,我们先看下前面两个
@Id
顾名思义,用来表明这家伙是主键,比较重要,需要特殊关照@GeneratedValue
设置初始值,谈到主键,我们一般会和”自增“这个一起说,所以你经常会看到的取值为strategy = GenerationType.IDENTITY
(由数据库自动生成)
这个注解主要提供了四种方式,分别说明如下
取值 | 说明 |
---|---|
GenerationType.TABLE | 使用一个特定的数据库表格来保存主键 |
GenerationType.SEQUENCE | 根据底层数据库的序列来生成主键,条件是数据库支持序列 |
GenerationType.IDENTITY | 主键由数据库自动生成(主要是自动增长型) |
GenerationType.AUTO | 主键由程序控制 |
关于这几种使用姿势,这里不详细展开了,有兴趣的可以可以看一下这博文: @GeneratedValue
c. Column注解
这个注解就是用来解决我们pojo成员名和数据库列名不一致的问题的,这个注解内部的属性也不少,相对容易理解,后面会单开一章来记录这些常用注解的说明查阅
d. CreateDate注解
这个注解和前面不一样的是它并非来自jpa-api
包,而是spring-data-common
包中提供的,表示会根据当前时间创建一个时间戳对象
e. 其他
到这里这个POJO已经创建完毕,后续的表中添加记录也可以直接使用它了,但是还有几个问题是没有明确答案的,先提出来,期待后文可以给出回答
- POJO属性的类型与表中类型
- mysql表中列可以有默认值,这个在POJO中怎么体现
- 一个表包含另一个表的主键时(主键关联,外键)等特殊的情况,POJO中有体现么?
2. Repository API声明
jpa非常有意思的一点就是你只需要创建一个接口就可以实现db操作,就这么神奇,可惜本文里面见不到太多神奇的用法,这块放在查询篇来见证奇迹
我们定义的API需要继承自org.springframework.data.repository.CrudRepository
,如下
package com.git.hui.boot.jpa.repository;import com.git.hui.boot.jpa.entity.MoneyPO;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.repository.CrudRepository;/** * 新增数据 * Created by @author yihui in 11:00 19/6/12. */public interface MoneyCreateRepository extends CrudRepository<MoneyPO, Integer> {}
好的,到这里就可以直接添加数据了 (感觉什么都没干,你居然告诉我可以插入数据???)
3. 使用姿势
a. 基础使用case
常规的使用姿势,无非单个插入和批量插入,我们先来看一下常规操作
@Componentpublic class JpaInsertDemo { @Autowired private MoneyCreateRepository moneyCreateRepository; public void testInsert() { addOne(); addMutl(); } private void addOne() { // 单个添加 MoneyPO moneyPO = new MoneyPO(); moneyPO.setName("jpa 一灰灰"); moneyPO.setMoney(1000L); moneyPO.setIsDeleted((byte) 0x00); Timestamp now = new Timestamp(System.currentTimeMillis()); moneyPO.setCreateAt(now); moneyPO.setUpdateAt(now); MoneyPO res = moneyCreateRepository.save(moneyPO); System.out.println("after insert res: " + res); } private void addMutl() { // 批量添加 MoneyPO moneyPO = new MoneyPO(); moneyPO.setName("batch jpa 一灰灰"); moneyPO.setMoney(1000L); moneyPO.setIsDeleted((byte) 0x00); Timestamp now = new Timestamp(System.currentTimeMillis()); moneyPO.setCreateAt(now); moneyPO.setUpdateAt(now); MoneyPO moneyPO2 = new MoneyPO(); moneyPO2.setName("batch jpa 一灰灰"); moneyPO2.setMoney(1000L); moneyPO2.setIsDeleted((byte) 0x00); moneyPO2.setCreateAt(now); moneyPO2.setUpdateAt(now); Iterable<MoneyPO> res = moneyCreateRepository.saveAll(Arrays.asList(moneyPO, moneyPO2)); System.out.println("after batchAdd res: " + res); }}
看下上面的两个插入方式,就这么简单,
- 通过IoC/DI注入 repository
- 创建PO对象,然后调用
save
,saveAll
方法就ok了
上面是一般的使用姿势,那么非一般使用姿势呢?
b. 插入时默认值支持方式
在创建表的时候,我们知道字段都有默认值,那么如果PO对象中某个成员我不传,可以插入成功么?会是默认的DB值么?
private void addWithNull() { // 单个添加 try { MoneyPO moneyPO = new MoneyPO(); moneyPO.setName("jpa 一灰灰 ex"); moneyPO.setMoney(2000L); moneyPO.setIsDeleted(null); MoneyPO res = moneyCreateRepository.save(moneyPO); System.out.println("after insert res: " + res); } catch (Exception e) { System.out.println("addWithNull field: " + e.getMessage()); }}
当看到上面的try/catch
可能就有预感,上面的执行多半要跪(