SpringData JPA 之疑难杂症

1、JPA自动建表不生成外键

SpringBoot项目搭配的JPA使用时候,有一对多的关系注解,那么自动会生成外键。外键在有些时候,会导致代码不能走通,我们不想要怎么做。有两种解决方案.

1.1、多表关系映射:一对多

情况一:单向一对多/多对一,在@JoinColumn内加上@ForeignKey属性配置即可

@OneToMany
@JoinColumn(name="cid",foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT))@ManyToOne
@JoinColumn(name="cid",foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT))

情况二:双向一对多,除了在@JoinColumn加@ForeignKey配置,还需要再另一方(一方)加配置

1.解决方案一:

// 一方加@org.hibernate.annotations.ForeignKey,注意:这个注解被废弃了,所以更新jar包的时候需要注意下
@org.hibernate.annotations.ForeignKey(name = “none”)
@OneToMany// 多方配置不变
@ManyToOne
@JoinColumn(name="cid",foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT))

2.解决方案二:

// 一方加@Transient,意思是忽略该字段,我认为加上这个字段后在加@OneToMany也无意义了.
@Transient
@OneToMany// 多方配置不变
@ManyToOne
@JoinColumn(name="cid",foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT))

2.2、多表关系映射:多对多

多对多也是类似的处理方式:

单向多对多:只要在@JoinTable内的@JoinColumn内加上@ForeignKey属性配置即可,记得连个表外键都要加

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name="tb_student_teacher",joinColumns=@JoinColumn(name="student_id",referencedColumnName = "student_id",foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT)),inverseJoinColumns=@JoinColumn(name="teacher_id",referencedColumnName = "teacher_id",foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT)))

双向多对多:参照双向一对多/多对一,在另一方加注解即可。

2、关于实体对象查询异常

WARNING: Please consider reporting this to the maintainers of org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Hibernate: sql语句
java.lang.StackOverflowErrorat java.base/java.lang.AbstractStringBuilder.inflate(AbstractStringBuilder.java:202)at java.base/java.lang.AbstractStringBuilder.putStringAt(AbstractStringBuilder.java:1639)at java.base/java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:513).......

关以这种错误大多都是编写的toString语句发生了错误,大家在写toString的时候一定要分清主表和从表,在编写主表的toString的时候一定要去除外键字段,而在编写从表的时候一定要加上外键字段,因为平时我们都是通过从表查询数据(因为从表有指向主表的外键),这样可以把主表的数据通过外键查询出来,但是主表上如果也有从表的字段的话就会一只循环,数据没完没了,所有抛异常也是对的,

总结一句话:从表有主表的字段,主表有从表的字段,打印从表顺带打印主表,但是主表里面还有从表字段,然后继续打印从表字段…

解决方案:

  1. 去除 @Data 注解,替换成 @Getter 和 @Setter 注解,使用@ToString(exclude = “引用类型字段”)
  2. 也可以重写toString()方法,把引用类型的输出删掉即可
  3. 查询后可以不要输出打印该对象,这样就不会死循环输出对象中的对象了(治标不治本, 不推荐)
  4. 在实体类上增加@JsonIgnoreProperties(value = "对方类引用本类的属性名")(我暂时试了是无效的,打印对象还是会报错)

3、关于延迟加载事务问题

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: cn.xw.domain.Teacher.students, could not initialize proxy - no Session at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)at org.hibernate.collection.internal.PersistentSet.toString(PersistentSet.java:327)at java.base/java.lang.String.valueOf(String.java:2801)at java.base/java.lang.StringBuilder.append(StringBuilder.java:135)

关于在使用延迟加载的时候,在当前的方法上必须设置@Transactional,因为在使用延迟加载底层已经使用了事务的相关方法

4、关于懒加载no session异常

org.hibernate.LazyInitializationException: failed to lazily initialize XXXXX could not initialize proxy - no Session

仔细看英文,懒加载存在,session 都关了,获取不到数据。

解决办法:lazy=“false”。若是用注解生成的。fetch = FetchType.EAGER

# 这个配置的意思就是在没有事务的情况下允许懒加载(可以理解为开启懒加载,实际上默认是开启的)
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

当然了,这样的话,你的缓存就不起作用了。另外解决办法,获取数据时,使用用 left join fetch 或 inner join fetch 语法。

5、关于数据库方言问题(Dialect)

6月 03, 2020 4:57:00 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
省去部分...
Hibernate: create table family (...) type=MyISAM
//上面一局为我们创建的是一张表并设置MyISAM引擎  错误就在这 无法运行了
6月 03, 2020 4:57:01 下午 org.hibernate.tool.schema.internal.ExceptionHandlerLoggedImpl handleException
WARN: GenerationTarget encountered exception accepting command : Error executing DDL "create table family (...) type=MyISAM" via JDBC Statement
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table family (...) type=MyISAM" via JDBC Statement
//从上面错误可以看出 程序运行的时候默认的数据库方言设置了 org.hibernate.dialect.MySQLDialect  而这个默认是MyISAM引擎

问题所在:因为我导入的hibernate坐标是5.4.10.Final,在导入这类高版本的坐标往往要为数据库方言设置MySQL5InnoDBDialect的配置,在我前面也测试了,关于坐标版本问题,发现5.0.x.Final左右的版本不用设置数据库方言,默认即可。

# 设置数据库方言,建表使用MyISAM,默认是InnoDB
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect

具体的版本在创建数据库表的时候会抛各种异常,这里我整理了一下数据库方言,方便大家参考

数据库方言
DB2                         org.hibernate.dialect.DB2Dialect
DB2 AS/400                  org.hibernate.dialect.DB2400Dialect
DB2 OS390                   org.hibernate.dialect.DB2390Dialect
PostgreSQL                  org.hibernate.dialect.PostgreSQLDialect
MySQL                       org.hibernate.dialect.MySQLDialect
MySQL with InnoDB           org.hibernate.dialect.MySQLInnoDBDialect
MySQL with MyISAM           org.hibernate.dialect.MySQLMyISAMDialect
Oracle (any version)        org.hibernate.dialect.OracleDialect
Oracle 9i/10g               org.hibernate.dialect.Oracle9Dialect
Sybase                      org.hibernate.dialect.SybaseDialect
Sybase Anywhere             org.hibernate.dialect.SybaseAnywhereDialect
Microsoft SQL Server        org.hibernate.dialect.SQLServerDialect
SAP DB                      org.hibernate.dialect.SAPDBDialect
Informix                    org.hibernate.dialect.InformixDialect
HypersonicSQL               org.hibernate.dialect.HSQLDialect
Ingres                      org.hibernate.dialect.IngresDialect
Progress                    org.hibernate.dialect.ProgressDialect
Mckoi SQL                   org.hibernate.dialect.MckoiDialect
Interbase                   org.hibernate.dialect.InterbaseDialect
Pointbase                   org.hibernate.dialect.PointbaseDialect
FrontBase                   org.hibernate.dialect.FrontbaseDialect
Firebird                    org.hibernate.dialect.FirebirdDialect

6、Executing an update/delete query

Caused by: javax.persistence.TransactionRequiredException: Executing an update/delete query

如果代码执行报如上错误,是由于在执行了更新删除操作没有加上事物,我们只需要加上@Transactional即可。不加会报错。

注意:有一种情况除外,就是在做单元测试的时候,加了@Transactiona也不起作用,必须加上@Transactiona + @Rollback(false),具体缘由参考下篇文章(SpringBoot Junit事物自动回滚)

7、SpringBoot Junit事物自动回滚

1、问题:在Spring单元测试类中的更新方法测试成功通过并没有报错,但数据库没有插入数据。

测试代码如下(执行测试方法后,数据库没有插入数据):

@Test
@Transactional
public void save() {User user = new User();user.setUsername("admin");user.setPassword("password");userRepository.save(user); // userRepository继承了JpaRepository
}

输出日志:

2022-01-27 15:39:29.022  INFO 6964 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@301ec38b testClass = DemoApplicationTests, testInstance = com.example.demo.DemoApplicationTests@57ea113a, testMethod = save@DemoApplicationTests, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@17a1e4ca testClass = DemoApplicationTests, locations = '{}', classes = '{class com.example.demo.DemoApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@194fad1, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@65f8f5ae, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@4d15107f, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@5a5a729f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@71687585, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@1e7c7811], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4348fa35]; rollback [true]
Hibernate: insert intosys_user(id, password, username) values(null, ?, ?)
2022-01-27 15:39:29.261  INFO 6964 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@301ec38b testClass = DemoApplicationTests, testInstance = com.example.demo.DemoApplicationTests@57ea113a, testMethod = save@DemoApplicationTests, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@17a1e4ca testClass = DemoApplicationTests, locations = '{}', classes = '{class com.example.demo.DemoApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@194fad1, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@65f8f5ae, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@4d15107f, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@5a5a729f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@71687585, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@1e7c7811], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]

**2、原因:**SpringBoot使用Junit编写单元测试,@Transactional默认是事物回滚的,这样测试的脏数据不影响数据库。具体看控制台输出关键日志部分:

Began transaction (1) for test context ....
Rolled back transaction for test: ....

**3、解决方法:**测试方法加上注解 @Rollback(true),关闭自动事物回滚。

@Test
@Transactional
@Rollback(false)
public void save() {User user = new User();user.setUsername("admin");user.setPassword("password");userRepository.save(user); // userRepository继承了JpaRepository
}

查看关键日志部分:

Began transaction (1) for test context ...
Committed transaction for test: ...

Spring Boot Junit事物自动回滚总结:

因为用了JPA配合Hibernate ,采用注解默认是开启了LayzLoad也就是懒加载,所以不得不在Junit的单元测试上加上@Transactional注解,这样Spring会自动为当前线程开启Session,这样在单元测试里面懒加载才不会因为访问完Repo之后,出现:session not found.,但是单元测试里面如果加上了@Transactional会自动回滚事务,需要在单元测试上面加上@Rollback(false)

8、对象属性设置值会自动更新数据库

使用Spring Data JPA获取的对象,事物开启时,其属性变更后自动更新数据库问题排查与解决方案

参考文献 & 鸣谢:转-Spring Data JPA中对象属性自动更新数据库:https://www.cnblogs.com/east7/p/14453910.html

问题描述

使用继承了JpaRepository的接口从数据库中获取到某个对象,然后操作这个对象的set属性值时,新值会直接更新到数据库。例如,UserRepository继承了JpaRepository,从数据库查询出User类实例user,当对user执行set属性修改时,新值会直接更新到数据库。

问题代码:(PS:出现这种情况有个前提条件,就是当前方法开启了事物)

@Test
@Transactional
@Rollback(false) // 由于是在junit中测试,所以必须取消自动回滚
public void testUpdate(){// 持久化状态,当前方法加了事物,SpringDataJPA在事物完成的时候,会自动提交修改User user = this.userRepository.findById(1L).get();user.setUsername("王小小");
}

输出日志:

Hibernate: selectuser0_.id as id1_0_0_,user0_.password as password2_0_0_,user0_.username as username3_0_0_ fromsys_user user0_ whereuser0_.id=?
Hibernate: updatesys_user setpassword=?,username=? whereid=?

可以发现查询完对象后,在给对象设置值后,就自动更新到数据库了

问题分析

由于方法上开启了事物,发现事务结束时会把属性发生改变的user对象更新到数据库,在控制台打印的日志中也可以看见相关update sql语句。百思不得其解,为什么从数据库中获取到的对象属性发生变更的时候,数据库就自动执行更新操作呢?原来是JPA的自带特性在作怪:Hibernate对象有四种状态,【持久态对象的属性被修改后,在数据库事务提交的时候,会更新数据库。】

回顾实体对象的四种状态以及转换关系

Spring Data JPA 可以理解为 是对 JPA 规范的二次封装和抽象,底层还是使用了 Hibernate 的 JPA 技术实现。因此,还是要不辞劳苦地翻阅Hibernate文档。最新的Hibernate文档中为Hibernate对象定义了四种状态(原来是三种状态,面试的时候基本上问的也是三种状态),分别是:瞬时态(new, or transient)、持久态(managed, or persistent)、游状态(detached)和移除态(removed,以前Hibernate文档中定义的三种状态中没有移除态),如下图所示,就以前的Hibernate文档中移除态被视为是瞬时态。

  • 瞬时态:当new一个实体对象后,这个对象处于瞬时态,即这个对象只是一个保存临时数据的内存区域,如果没有变量引用这个对象,则会被JVM的垃圾回收机制回收。这个对象所保存的数据与数据库没有任何关系,除非通过Session的save()、saveOrUpdate()、persist()、merge()方法把瞬时态对象与数据库关联,并把数据插入或者更新到数据库,这个对象才转换为持久态对象。
  • 持久态:持久态对象的实例在数据库中有对应的记录,并拥有一个持久化标识(ID)。对持久态对象进行delete操作后,数据库中对应的记录将被删除,那么持久态对象与数据库记录不再存在对应关系,持久态对象变成移除态(可以视为瞬时态)。持久态对象被修改变更后,不会马上同步到数据库,直到数据库事务提交。
  • 游离态:当Session进行了close()、clear()、evict()或flush()后,实体对象从持久态变成游离态,对象虽然拥有持久和与数据库对应记录一致的标识值,但是因为对象已经从会话中清除掉,对象不在持久化管理之内,所以处于游离态(也叫脱管态)。游离态的对象与临时状态对象是十分相似的,只是它还含有持久化标识。
  • 移除态:当调用EntityManger对实体进行delete后,该实体对象就处于删除状态。其本质也就是一个瞬时状态的对象

问题解决

1、更新的方法不开启事物处理,然后使用save()等方法更新操作即可(缺点:没有事物无法回滚,不能保证数据一致性)

@Test
public void testUpdate(){// 持久化状态的,不过没有加事物控制User user = this.userCrudRepository.findById(1L).get();user.setUsername("王小红");this.userCrudRepository.save(user);
}

2、因持久层框架JPA自身的机制,会在事务提交后将持久状态的对象自动更新到数据库,为了避免自动更新,我们可以创建一个对象,设置好属性后再调用save()方法更新数据(缺点:比较繁琐,代码也会变得不够简洁)

@Test
@Transactional
public void testUpdate(){// 持久化状态的,不过没有加事物控制User user = this.userCrudRepository.findById(1L).get();User userNew = new User();userNew.setId(user.getId)userNew.setUsername("admin");userNew.setPassword(user.getPassword);this.userCrudRepository.save(userNew);
}

3、把持久态的对象变成游离态即可,有如下三种方法(缺点也是需要注入EntityManager)

  1. close() 方法:关闭session可以,但是若后面还要用session这个方法就不太好了
  2. clear() 方法:将session中所有的对象全部清除缓存
  3. evict() 方法:将某一个对象清楚缓存session(推荐)
@PersistenceContext
private EntityManager entityManager;@Test
@Transactional
public void testUpdate(){// 持久化状态的,不过没有加事物控制User user = this.userCrudRepository.findById(1L).get();// 检查对象是否是持久化态if (entityManager.contains(user)) {// 获取sessionSession session = entityManager.unwrap(org.hibernate.Session.class);// 转换成游离态session.evict(entry);}user.setUsername("王小红");this.userCrudRepository.save(user);
}

SpringData JPA 之疑难杂症相关推荐

  1. 带你搭一个SpringBoot+SpringData JPA的环境

    前言 只有光头才能变强. 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y 不知道大家对SpringBoot和Spring Da ...

  2. idea加入springboot插件_带你搭一个SpringBoot+SpringData JPA的环境

    前言 只有光头才能变强. 不知道大家对SpringBoot和Spring Data JPA了解多少,如果你已经学过Spring和Hibernate的话,那么SpringBoot和SpringData ...

  3. SpringData JPA条件查询、排序、分页查询

    前言 在刚开始学习的时候,在dao的定义的接口需要继承JpaRepository<T, ID>接口和JpaSpecificationExecutor< T >接口,但是一直以来 ...

  4. spring boot 整合多数据源JDBC、多数据源mybatis、多数据源springdata jpa

    目录 代码地址:(spring-boot github地址) 1.springboot整合JDBC 2.springboot整合mybatis 3.springboot整合springdata jpa ...

  5. SpringBoot简介、SpringBoot 入门程序搭建、与JDBC、Druid、Mybatis和SpringData JPA的整合

    一.SpringBoot 简介: spring boot并不是一个全新的框架,它不是spring解决方案的一个替代品,而是spring的一个封装.所以,你以前可以用spring做的事情,现在用spri ...

  6. SpringData+JPA+mysql, cannot be null when ‘hibernate.dialect‘ not set

    SpringData+JPA+mysql 8, 报错 cannot be null when 'hibernate.dialect' not set 是因为Hibernate SQL方言没有设置导致的 ...

  7. 【持久层框架】- SpringData - JPA

    SpringData - JPA

  8. 基于springdata JPA的dao层接口实现

    基于springdata JPA的dao层接口实现以及分页 1.[举例] 只需要继承 JpaRepository<实体类,主键类型> package com.tmall.tmallspri ...

  9. SpringData JPA 详解(自定义查询、分页、事务控制)

    简介 SpringData JPA是 JPA的一种实现,极大的简化了JPA的开发,原始JPA的开发,需要创建实体管理工厂,使用实体管理器定义各种查询进行CRUD操作,而SpringData JPA只需 ...

最新文章

  1. Linux 常用命令九 tar
  2. LNMP添加、删除虚拟主机及伪静态使用教程
  3. easyui分页查询为什么会有下拉框_做网站优化为什么要分析百度下拉词和相关搜索?...
  4. 需求管理是CMM可重复级中的6个关键过程域之一,其主要目标是__________。A.客观地验证需求管理活动...
  5. Condition源码分析
  6. 浅谈微信小程序对于房地产行业的影响
  7. debian换源_WSL2安装Debian(Ubuntu)并配置国内apt源
  8. C++面向对象程序设计课程笔记(第三周)
  9. net472无法建立到信任_是否还会信任,那个曾经背叛过自己的人
  10. VSD Viewer for Mac(Visio绘图文件阅读器)
  11. Kafka消息队列原理总结
  12. Non-managed pom.xml file found
  13. 这台笔记本最适合程序员编程!!
  14. bugku crypto-python_jail
  15. 一缕黑暗中的火光-----------用例图--------------优雅的建模语
  16. 古早软件 vim的使用
  17. 【JavaWeb学习】CSS(选择器)
  18. Verilog 语言编写 OV7725摄像头初始化寄存器库与模块的初始化
  19. 极大似然估计,最大后验估计的区别
  20. flex布局垂直居中

热门文章

  1. 坐标转换:将imu坐标系下的角速度、线速度转换到车体坐标系,参考Autoware
  2. Capacitor 新一代混合应用“神器” 会代替Cordova吗??
  3. oracle 查询group by的字段之外的字段
  4. 手写喜马拉雅APP特效
  5. LNMP架构搭建以及部署DISCUZ!社区论坛应用
  6. javascript 框架_Javascript框架的成本
  7. 「RPC」简述RPC
  8. Ubuntu安装nvidia显卡驱动经验和注意事项(成功率高)
  9. 华为mysql面试题_华为JAVA开发工程师面试经验
  10. 云计算的概念、原理和关键技术