使用SpringBoot-JPA进行自定义保存及批量保存功能
作者:鹏徙南暝 发布时间:2022-05-26 22:42:43
说明
SpringBoot版本:2.1.4.RELEASE
java版本:1.8
文中所说JPA皆指spring-boot-starter-data-jpa
使用JPA保存一个Student对象
在JPA中保存一个对象,仅需要该对象,一个仓储即可。
StudentDO实体类:
@Getter
@Setter
@Entity
@Table(name = "t_student")
public class StudentDO {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column
private Long id;
@Column
private String seq;
@Column
private String name;
@Column
private int sex;
}JPA仓储:
@Repository
public interface StudentRepo extends JpaRepository<StudentDO, String> {
}
一般的,我们只需要调用StudentRepo.save()
方法即可完成对实体对象的保存操作。
@Test
public void testSave() {
StudentDO student = new StudentDO();
student.setName("张三");
student.setSex(1);
student.setSeq("123456");
studentRepo.save(student);
Assert.assertNotNull(student.getId());
}
在插入过程中使用mysql函数
如果我们希望student的seq值由系统自动生成,且生成规则为“yyMMdd + 8位自增序列”(例如19060310000000)又该如何实现呢?
首先想到的是该如何生成这一串序列,mysql不像oracle自身支持sequence,因此在这里可以借用函数以及额外的sequence表来实现这一操作,网上有很多实现方式,这里就不再赘述。
现在已经有了函数getseq('student_seq')
可以获取到该序列,该如何将其应用到保存对象的方法中?显然的一个问题是,像上面那样再直接调用save方法已经行不通了,应该得需要自定义插入的sql实现。
一个容易想到的办法是,在StudentDO类上使用注解@SQLInsert来定义insert的实现,它写起来应该会像这个样子:
@SQLInsert(sql = "INSERT INTO t_student(seq, name, sex) VALUES (getseq('student_seq'), ?, ?")
这条sql语句本身并没有什么问题,再次调用save()方法也确实能够执行。但是很可惜,它确会抛出一个sql异常:
java.sql.SQLException: Parameter index out of range (3 > number of parameters, which is 2).
显然是程序认为有多少个参数,就得有多少个“?”与之匹配,目前我并没有找到解决这个问题的方案,所以这种方法宣告失败。
既然@SQLInsert行不通,或许可以考虑使用@Query注解来自定义一个实现。我们可以在StudentRepo中定义这样一个方法:
@Transactional
@Modifying
@Query(value = "INSERT INTO t_student(seq, name, sex) VALUES (getseq('student_seq'), :#{#student.name}, :#{#student.sex})", nativeQuery = true)
int insert(@Param("student") StudentDO student);
试着运行一下,结果很成功,对象被正常的存储到数据库中,并且seq的取值也正常。看上去我们的问题已经得到了解决,但事实真的如此么?
自定义的批量存储
上面的例子中,我们成功通过JPA调用了mysql函数将对象存储到数据库中。但上面的例子只提供了单个保存的方法,如果我们想批量保存呢?@Query里面的sql能够进行改造么?我并没有找到@Query中使用List作为参数的insert方法,但是这并不代表这一操作不能执行。JPA仍旧提供给了使用者原始的使用方式:利用EntityManager来构造sql并执行。
@PersistenceContext
private EntityManager entityManager;
private int batchInsert(List<StudentDO> students) {
StringBuilder sb = new StringBuilder();
sb.append("INSERT INTO t_student(seq, name, sex) VALUES ");
for(StudentDO student : students) {
sb.append("(getseq('student_seq'), ?, ?),");
}
String sql = sb.toString().substring(0, sb.length() - 1);
Query query = entityManager.createNativeQuery(sql);
int paramIndex = 1;
for(StudentDO student : students) {
query.setParameter(paramIndex++, student.getName());
query.setParameter(paramIndex++, student.getSex());
}
return query.executeUpdate();
}
就像MyBatis一样,使用者也可以自定义SQL来执行,试试看,同样没有问题,再多的数据也可以被保存到数据库中!批量保存的效果达到了。
再仔细想一想,通过上面的过程,还有什么问题么?对比JPA自带的save()方法,似乎我们的自定义保存返回的都是int结果,也就是操作影响的数据库行数。使用过JPA的人都应该了解,JPA的save()方法(或者其他JPA方法)返回的对象是经过持久化的,得益于这一特性,使用者可以在调用save()方法之后获取到对象的id等必须先插入到数据库之后才会有的值。显然这里的操作已经失去了这一特性,那如果我们把返回值对应的改为Object或者List可以做到么?答案是并不能,我们会得到如下异常:
org.springframework.dao.InvalidDataAccessApiUsageException: Modifying queries can only use void or int/Integer as return type!; nested exception is java.lang.IllegalArgumentException: Modifying queries can only use void or int/Integer as return type!
insert方法必须使用@Modifying进行注解,而@Modifying注解的方法又只能返回int类型的结果。这种情况下或许只能先利
用查询得到seq的值再进行操作。
总结
对于JPA的使用还不够了解,一些复杂的情况下没有找到最理想的实现方案。
@Query注解中是否能够使用List以及实现动态拼接参数的效果没有得到解决
自定义的sql语句返回持久化对象的问题没有方案
在以后的使用了解中希望能够找到解决办法,将问题记录在这里,以便后续查看。
以上所述是小编给大家介绍的使用SpringBoot-JPA进行自定义保存及批量保存功能,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!
来源:https://www.cnblogs.com/jptangchina/p/11023041.html


猜你喜欢
- 前言本篇文章会说下如何使用并且要用麻烦的自定义 view 去实现时间轴效果,以及如何分析、实现自定义 view。 需要具备的知识:Paint
- C++/java 继承类的多态详解学过C++和Java的人都知道,他们二者由于都可以进行面向对象编程,而面向对象编程的三大特性就是封装、继承
- 实现目标通过C#实现电脑的注销、关机、重启功能知识点本案例涉及的知识点包含:Process、Shell32.dll、User32.dll、S
- 如何实现封装可以分为两步:第一步:将类的变量声明为private。第二步:提供公共set和get方法来修改和获取变量的值。代码展示publi
- 1.使用WINDOWS API/// <summary> /// 判断一个磁盘驱动器的类型 /// </summary&g
- 先来看问题纠结了几个小时终于找到了问题所在,因为shiro的realm属于Filter,简单说就是初始化realm时,spring还未加载相
- 一、堆的概念堆的定义:n个元素的序列{k1 , k2 , … , kn}称之为堆,当且仅当满足以下条件时:(1)ki
- 我有一个文本输入对话框,当我点击对话框上的“是”按钮,它会验证输入,然后关闭对话框。但是,如果输入错误,我想停留在同一个对话框中.。每一次,
- 本文实例为大家分享了Android下载进度监听和通知的具体代码,供大家参考,具体内容如下下载管理器关于下载进度的监听,这个比较简单,以apk
- 一般而言,Android 应用在请求数据时都是以 Get 或 Post 等方式向远程服务器发起请求,那你有没有想过其实我们也可以在 Andr
- 前言今天记录一个前段时间遇到的一个小问题的解决方法, 跨域!!!相信跨域这个问题, 做开发的或多或少都遇到过, 而且已经有很多博主已经分享了
- Android之文件数据存储一、文件保存数据介绍Activity提供了openFileOutput()方法可以用于把数据输出到文件中,具体的
- 前一篇文章《C#影院售票系统毕业设计(2)》中总结了动态绘制控件、票类型的切换以及数据在窗体中的展现。今天继续总结!本文总结项目中最核心的部
- 面试官:sychronized关键字有哪些特性?应聘者:可以用来修饰方法;可以用来修饰代码块;可以用来修饰静态方法;可以保证线程安全;支持锁
- 最近在做一个资源共享的项目中,采用了Struts2.1.8+Spr
- 用Java编写简单的五子棋,供大家参考,具体内容如下前言这两天在空闲时间做了个五子棋项目,分享给大家看一下,界面是这样的:界面很丑我知道,本
- 本文实例讲述了Android获取SD卡路径及SDCard内存的方法。分享给大家供大家参考。具体分析如下:昨天在研究拍照后突破的存储路径的问题
- Java常用类String类概述String类:代表字符串。Java程序中的所有字符串字面值(如:”abc“)都作为子类的实例实现Strin
- 目录前言线程基础1、创建线程2、暂停线程3、线程等待4、线程终止C#中的lock关键字总结前言最近由于工作的需要,一直在使用C#的多线程进行
- 匿名类是不能有名称的类,所以没办法引用它们。必须在创建时,作为new语句的一部分来声明它们。这就要采用另一种形式的new语句,如下所示: n