当前位置: 首页 > news >正文

太原网站维护网站建设中标公告

太原网站维护,网站建设中标公告,小型企业网站的设计与实现,Wordpress 新建标签Spring Data Jpa 简介JPAJPA(Java Persistence API)意即Java持久化API#xff0c;是Sun官方在JDK5.0后提出的Java持久化规范(JSR 338#xff0c;这些接口所在包为javax.persistence#xff0c;详细内容可参考https://github.com/javaee/jpa-spec)JPA的出现主要是为了简化持久…Spring Data Jpa 简介JPAJPA(Java Persistence API)意即Java持久化API是Sun官方在JDK5.0后提出的Java持久化规范(JSR 338这些接口所在包为javax.persistence详细内容可参考https://github.com/javaee/jpa-spec)JPA的出现主要是为了简化持久层开发以及整合ORM技术结束Hibernate、TopLink、JDO等ORM框架各自为营的局面。JPA是在吸收现有ORM框架的基础上发展而来易于使用伸缩性强。总的来说JPA包括以下3方面的技术ORM映射元数据 支持XML和注解两种元数据的形式元数据描述对象和表之间的映射关系API 操作实体对象来执行CRUD操作查询语言 通过面向对象而非面向数据库的查询语言(JPQL)查询数据避免程序的SQL语句紧密耦合JPA架构Spring Data JpaSpring Data Jpa官方解释Spring Data JPA是Spring Data家族的一部分可以轻松实现基于JPA的存储库。 此模块处理对基于JPA的数据访问层的增强支持。 它使构建使用数据访问技术的Spring驱动应用程序变得更加容易。在相当长的一段时间内实现应用程序的数据访问层一直很麻烦。 必须编写太多样板代码来执行简单查询以及执行分页和审计。 Spring Data JPA旨在通过减少实际需要的工作量来显著改善数据访问层的实现。 作为开发人员您编写repository接口包括自定义查找器方法Spring将自动提供实现。Spring Data生态Jpa、Hibernate、Spring Data Jpa三者之间的关系总的来说JPA是ORM规范Hibernate、TopLink等是JPA规范的具体实现这样的好处是开发者可以面向JPA规范进行持久层的开发而底层的实现则是可以切换的。Spring Data Jpa则是在JPA之上添加另一层抽象(Repository层的实现)极大地简化持久层开发及ORM框架切换的成本。Jpa、Hibernate、Spring Data Jpa三者之间的关系Spring Data Jpa的java配置方案在Spring Boot没出来之前如果要采用Java Configuration来配置Spring Data Jpa你需要配置如下的Bean参考自Spring In Action及Spring Data Jpa官方文档5.1.2. Annotation-based Configurationimport org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.jpa.repository.config.EnableJpaRepositories;import org.springframework.orm.jpa.JpaTransactionManager;import org.springframework.orm.jpa.JpaVendorAdapter;import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;import org.springframework.orm.jpa.vendor.Database;import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.persistence.EntityManagerFactory;import javax.sql.DataSource;/*** 注意spring-data-jpa2.x版本需要spring版本为5.x* 否则会报Initialization of bean failed; nested exception is java.lang.AbstractMethodError错误* 参考https://stackoverflow.com/questions/47558017/error-starting-a-spring-application-initialization-of-bean-failed-nested-excep* 搭配方案spring4spring-data-jpa1.x或spring5spring-data-jpa2.x*/Configuration// 借助spring data实现自动化的jpa repository只需编写接口无需编写实现类// 相当于xml配置的// repositoryImplementationPostfix默认就是Impl// entityManagerFactoryRef默认就是entityManagerFactory// transactionManagerRef默认就是transactionManagerEnableJpaRepositories(basePackages {com.example.repository},repositoryImplementationPostfix Impl,entityManagerFactoryRef entityManagerFactory,transactionManagerRef transactionManager)EnableTransactionManagement // 启用事务管理器public class SpringDataJpaConfig {// 配置jpa厂商适配器(参见spring实战p320)Beanpublic JpaVendorAdapter jpaVendorAdapter() {HibernateJpaVendorAdapter jpaVendorAdapter new HibernateJpaVendorAdapter();// 设置数据库类型(可使用org.springframework.orm.jpa.vendor包下的Database枚举类)jpaVendorAdapter.setDatabase(Database.MYSQL);// 设置打印sql语句jpaVendorAdapter.setShowSql(true);// 设置不生成ddl语句jpaVendorAdapter.setGenerateDdl(false);// 设置hibernate方言jpaVendorAdapter.setDatabasePlatform(org.hibernate.dialect.MySQL5Dialect);return jpaVendorAdapter;}// 配置实体管理器工厂Beanpublic LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {LocalContainerEntityManagerFactoryBean emfb new LocalContainerEntityManagerFactoryBean();// 注入数据源emfb.setDataSource(dataSource);// 注入jpa厂商适配器emfb.setJpaVendorAdapter(jpaVendorAdapter);// 设置扫描基本包emfb.setPackagesToScan(com.example.entity);return emfb;}// 配置jpa事务管理器Beanpublic PlatformTransactionManager transactionManager(EntityManagerFactory emf) {JpaTransactionManager transactionManager new JpaTransactionManager();// 配置实体管理器工厂transactionManager.setEntityManagerFactory(emf);return transactionManager;}}启用web支持还需要在Spring MVC配置类上添加EnableSpringDataWebSupport注解ConfigurationComponentScan(basePackages {cn.fulgens.controller})EnableWebMvc // 启用spring mvcEnableSpringDataWebSupport // 启用springmvc对spring data的支持public class WebMvcConfig extends WebMvcConfigurerAdapter {}Spring Boot整合Spring Data Jpa导入依赖org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-data-jpamysqlmysql-connector-javaruntimeorg.projectlomboklomboktrueorg.springframework.bootspring-boot-devtoolstrueorg.springframework.bootspring-boot-starter-testtest相关配置server:port: 8080servlet:context-path: /spring:datasource:url: jdbc:mysql://localhost:3306/test?useUnicodetruecharacterEncodingUTF-8zeroDateTimeBehaviorconvertToNullallowMultiQueriestrueuseSSLfalseusername: rootpassword: mysql123jpa:database: MySQLdatabase-platform: org.hibernate.dialect.MySQL5InnoDBDialectshow-sql: truehibernate:ddl-auto: updateddl-autocreate每次运行程序时都会重新创建表故而数据会丢失create-drop每次运行程序时会先创建表结构然后待程序结束时清空表upadte每次运行程序没有表时会创建表如果对象发生改变会更新表结构原有数据不会清空只会更新(推荐使用)validate运行程序会校验数据与数据库的字段类型是否相同字段不同会报错none: 禁用DDL处理注意Spring Data Jpa的使用Spring Data Jpa UML类图Spring Data Jpa UML简单的REST CRUD示例实体类/src/main/java/com/example/springbootjpa/entity/Userpackage com.example.springbootjpa.entity;import lombok.Data;import org.hibernate.annotations.GenericGenerator;import javax.persistence.*;EntityTable(name tb_user)Datapublic class User {IdGenericGenerator(name idGenerator, strategy uuid)GeneratedValue(generator idGenerator)private String id;Column(name username, unique true, nullable false, length 64)private String username;Column(name password, nullable false, length 64)private String password;Column(name email, length 64)private String email;}主键采用UUID策略GenericGenerator是Hibernate提供的主键生成策略注解注意下面的GeneratedValue(JPA注解)使用generator idGenerator引用了上面的name idGenerator主键生成策略一般简单的Demo示例中只会使用GeneratedValue(strategy GenerationType.IDENTITY)这种主键自增的策略而实际数据库中表字段主键类型很少是int型的JPA自带的几种主键生成策略TABLE 使用一个特定的数据库表格来保存主键SEQUENCE 根据底层数据库的序列来生成主键条件是数据库支持序列。这个值要与generator一起使用generator 指定生成主键使用的生成器(可能是orcale中自己编写的序列)IDENTITY 主键由数据库自动生成(主要是支持自动增长的数据库如mysql)AUTO 主键由程序控制也是GenerationType的默认值Dao层/src/main/java/com/example/springbootjpa/repository/UserRepositorypackage com.example.springbootjpa.repository;import com.example.springbootjpa.entity.User;import org.springframework.data.jpa.repository.JpaRepository;public interface UserRepository extends JpaRepository {}Controller层这里简单起见省略Service层/src/main/java/com/example/springbootjpa/controller/UserControllerpackage com.example.springbootjpa.controller;import com.example.springbootjpa.entity.User;import com.example.springbootjpa.repository.UserRepository;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.domain.Page;import org.springframework.data.domain.PageRequest;import org.springframework.data.domain.Pageable;import org.springframework.web.bind.annotation.*;import java.util.HashMap;import java.util.Optional;RestControllerRequestMapping(/users)public class UserController {Autowiredprivate UserRepository userRepository;PostMapping()public User saveUser(RequestBody User user) {return userRepository.save(user);}DeleteMapping(/{id})public void deleteUser(PathVariable(id) String userId) {userRepository.deleteById(userId);}PutMapping(/{id})public User updateUser(PathVariable(id) String userId, RequestBody User user) {user.setId(userId);return userRepository.saveAndFlush(user);}GetMapping(/{id})public User getUserInfo(PathVariable(id) String userId) {Optional optional userRepository.findById(userId);return optional.orElseGet(User::new);}GetMapping(/list)public Page pageQuery(RequestParam(value pageNum, defaultValue 1) Integer pageNum,RequestParam(value pageSize, defaultValue 10) Integer pageSize) {return userRepository.findAll(PageRequest.of(pageNum - 1, pageSize));}}Spring Data Jpa使用详解Spring Data查询方法使用Spring Data创建查询只需四步声明一个接口继承自Repository或Repositoy的一个子接口对于Spring Data Jpa通常是JpaRepository如interface PersonRepository extends Repository { … }在接口中声明查询方法如interface PersonRepository extends Repository {List findByLastname(String lastname);}使用 JavaConfig 或 XML configuration配置Spring让 Spring 为声明的接口创建代理对象3.1 JavaConfig参见上文3.2 使用Xml配置可以像下面这样使用jpa命名空间进行配置xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:jpahttp://www.springframework.org/schema/data/jpaxsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/data/jpahttp://www.springframework.org/schema/data/jpa/spring-jpa.xsd顺带一提对于不同的Spring Data子项目Spring提供了不同的xml命名空间如对于Spring Data MongoDB可以将上面的jpa改为mongodb当然使用Spring Boot这一步基本可以省略我们需要做的就是在application.properties或application.yml文件中配置几个属性即可注入Repository实例并使用如class SomeClient {private final PersonRepository repository;SomeClient(PersonRepository repository) {this.repository repository;}void doSomething() {List persons repository.findByLastname(Matthews);}}定义Repository接口选择性暴露CRUD方法一种方法是定义一个BaseRepository接口继承Repository接口并从CrudRepository中copy你想暴露的CRUD方法src/main/java/com/example/springbootjpa/repository/MyBaseRepositorypackage com.example.springbootjpa.repository;import org.springframework.data.repository.NoRepositoryBean;import org.springframework.data.repository.Repository;import java.util.Optional;/*** 自定义Repository选择性暴露CRUD方法* param * param */NoRepositoryBeanpublic interface MyBaseRepository extends Repository {Optional findById(ID id); S save(S entity);}注意MyBaseRepository上面加了NoRepositoryBean注解src/main/java/com/example/springbootjpa/repository/UserRepository2package com.example.springbootjpa.repository;import com.example.springbootjpa.entity.User;import org.springframework.stereotype.Repository;public interface UserRepository2 extends MyBaseRepository {}Junit测试package com.example.springbootjpa.repository;import org.junit.Assert;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import java.util.Optional;RunWith(SpringRunner.class)SpringBootTestpublic class UserRepository2Test {Autowiredprivate UserRepository2 userRepositoy;Testpublic void findByIdTest() {Optional optional userRepositoy.findById(40289f0c65674a930165674d54940000);Assert.assertNotNull(optional.get());}}这里启动Junit测试时报了一个错记录一下java.lang.IllegalStateException: Failed to load ApplicationContext...Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBExceptionat java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:190)at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:499)... 50 more错误很明显Spring应用上下文加载失败原因是找不到javax.xml.bind.JAXBException手贱从java8升级到java10JAXB API是java EE 的API在java SE 9.0 中已经不再包含这个 Jar 包。java9 中引入了模块的概念默认情况下Java SE中将不再包含java EE 的Jar包而在 java 6/7 / 8 时关于这个API 都是捆绑在一起的解决方法添加如下jar包javax.xml.bindjaxb-api2.3.0com.sun.xml.bindjaxb-impl2.3.0com.sun.xml.bindjaxb-core2.3.0javax.activationactivation1.1.1另一种方法是使用RepositoryDefinition注解并从CrudRepository中copy你想暴露的CRUD方法src/main/java/com/example/springbootjpa/repository/UserRepository3package com.example.springbootjpa.repository;import com.example.springbootjpa.entity.User;import org.springframework.data.repository.RepositoryDefinition;import java.util.Optional;RepositoryDefinition(domainClass User.class, idClass String.class)public interface UserRepository3 {Optional findById(String id);User save(User user);}Repository方法的Null值处理从Spring Data2.0开始对于返回单个聚合实例的CRUD方法可以使用java8 Optional接口作为方法返回值来表明可能存在的缺省值典型示例为CrudRepository的findById方法另外Spring也提供了几个注解来处理Null值NonNullApi: 在包级别使用来声明参数和返回值不能为NullNonNull: 在参数或返回值上使用当它们不能为Null时(如果在包级别上使用了NonNullApi注解则没有必要再使用NonNull注解了)Nullable: 在参数或返回值上使用当它们可以为Null时查询方法查询创建Query CreationSpring Data Jpa通过解析方法名创建查询框架在进行方法名解析时会先把方法名多余的前缀find…By, read…By, query…By, count…By以及get…By截取掉然后对剩下部分进行解析第一个By会被用作分隔符来指示实际查询条件的开始。 我们可以在实体属性上定义条件并将它们与And和Or连接起来从而创建大量查询User findByUsername(String username);List findByUsernameIgnoreCase(String username);List findByUsernameLike(String username);User findByUsernameAndPassword(String username, String password);User findByEmail(String email);List findByEmailLike(String email);List findByIdIn(List ids);List findByIdInOrderByUsername(List ids);void deleteByIdIn(List ids);Long countByUsernameLike(String username);支持的关键字、示例及JPQL片段如下表所示KeywordSampleJPQL snippetAndfindByLastnameAndFirstname… where x.lastname ?1 and x.firstname ?2OrfindByLastnameOrFirstname… where x.lastname ?1 or x.firstname ?2Is,EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEquals… where x.firstname ?1BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2LessThanfindByAgeLessThan… where x.age ?1LessThanEqualfindByAgeLessThanEqual… where x.age ?1GreaterThanfindByAgeGreaterThan… where x.age ?1GreaterThanEqualfindByAgeGreaterThanEqual… where x.age ?1AfterfindByStartDateAfter… where x.startDate ?1BeforefindByStartDateBefore… where x.startDate ?1IsNullfindByAgeIsNull… where x.age is nullIsNotNull,NotNullfindByAge(Is)NotNull… where x.age not nullLikefindByFirstnameLike… where x.firstname like ?1NotLikefindByFirstnameNotLike... findByFirstnameNotLikeStartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)OrderByfindByAgeOrderByLastnameDesc… where x.age ?1 order by x.lastname descNotfindByLastnameNot… where x.lastname ?1InfindByAgeIn(Collection ages)… where x.age in ?1NotInfindByAgeNotIn(Collection ages)… where x.age not in ?1TruefindByActiveTrue()… where x.active trueFalsefindByActiveFalse()… where x.active falseIgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) UPPER(?1)限制查询结果Spring Data Jpa支持使用first、top以及Distinct 关键字来限制查询结果如User findFirstByUsernameOrderByUsernameAsc(String username);List findTop10ByUsername(String username, Sort sort);List findTop10ByUsername(String username, Pageable pageable);自定义查询Using QueryQuery 注解的使用非常简单只需在声明的方法上面标注该注解同时提供一个 JPQL 查询语句即可Query(select u from User u where u.email ?1)User getByEmail(String eamil);Query(select u from User u where u.username ?1 and u.password ?2)User getByUsernameAndPassword(String username, String password);Query(select u from User u where u.username like %?1%)List getByUsernameLike(String username);使用命名参数Using Named Parameters默认情况下Spring Data JPA使用基于位置的参数绑定如前面所有示例中所述。 这使得查询方法在重构参数位置时容易出错。 要解决此问题可以使用Param注解为方法参数指定具体名称并在查询中绑定名称如以下示例所示Query(select u from User u where u.id :id)User getById(Param(id) String userId);Query(select u from User u where u.username :username or u.email :email)User getByUsernameOrEmail(Param(username) String username, Param(email) String email);Using SpEL Expressions从Spring Data JPA release 1.4开始Spring Data JPA支持名为entityName的变量。 它的用法是select x from #{#entityName} x。 entityName的解析方式如下如果实体类在Entity注解上设置了name属性则使用它。 否则使用实体类的简单类名。为避免在Query注解使用实际的实体类名就可以使用#{#entityName}进行代替。如以上示例中Query注解的查询字符串里的User都可替换为#{#entityName}Query(select u from #{#entityName} u where u.email ?1)User getByEmail(String eamil);原生查询Native QueriesQuery注解还支持通过将nativeQuery标志设置为true来执行原生查询同样支持基于位置的参数绑定及命名参数如Query(value select * from tb_user u where u.email ?1, nativeQuery true)User queryByEmail(String email);Query(value select * from tb_user u where u.email :email, nativeQuery true)User queryByEmail(Param(email) String email);注意Spring Data Jpa目前不支持对原生查询进行动态排序但可以通过自己指定计数查询countQuery来使用原生查询进行分页、排序如Query(value select * from tb_user u where u.username like %?1%,countQuery select count(1) from tb_user u where u.username %?1%,nativeQuery true)Page queryByUsernameLike(String username, Pageable pageable);分页查询及排序Spring Data Jpa可以在方法参数中直接传入Pageable或Sort来完成动态分页或排序通常Pageable或Sort会是方法的最后一个参数如Query(select u from User u where u.username like %?1%)Page findByUsernameLike(String username, Pageable pageable);Query(select u from User u where u.username like %?1%)List findByUsernameAndSort(String username, Sort sort);那调用repository方法时传入什么参数呢对于Pageable参数在Spring Data 2.0之前我们可以new一个org.springframework.data.domain.PageRequest对象现在这些构造方法已经废弃取而代之Spring推荐我们使用PageRequest的of方法new PageRequest(0, 5);new PageRequest(0, 5, Sort.Direction.ASC, username);new PageRequest(0, 5, new Sort(Sort.Direction.ASC, username));PageRequest.of(0, 5);PageRequest.of(0, 5, Sort.Direction.ASC, username);PageRequest.of(0, 5, Sort.by(Sort.Direction.ASC, username));注意Spring Data PageRequest的page参数是从0开始的 zero-based page index对于Sort参数同样可以new一个org.springframework.data.domain.Sort但推荐使用Sort.by方法自定义修改、删除 Modifying Queries单独使用Query注解只是查询如涉及到修改、删除则需要再加上Modifying注解如Transactional()ModifyingQuery(update User u set u.password ?2 where u.username ?1)int updatePasswordByUsername(String username, String password);Transactional()ModifyingQuery(delete from User where username ?1)void deleteByUsername(String username);注意Modifying queries can only use void or int/Integer as return type!多表查询这里使用级联查询进行多表的关联查询多对多/src/main/java/com/example/springbootjpa/entity/Userpackage com.example.springbootjpa.entity;import lombok.Data;import org.hibernate.annotations.GenericGenerator;import javax.persistence.*;import java.util.Date;import java.util.Set;import java.util.UUID;EntityTable(name tb_user)Datapublic class User {IdGenericGenerator(name idGenerator, strategy uuid)GeneratedValue(generator idGenerator)private String id;Column(name username, unique true, nullable false, length 64)private String username;Column(name password, nullable false, length 64)private String password;Column(name email, unique true, length 64)private String email;ManyToMany(targetEntity Role.class, cascade {CascadeType.PERSIST, CascadeType.MERGE}, fetch FetchType.LAZY)JoinTable(name tb_user_role, joinColumns {JoinColumn(name user_id, referencedColumnName id)},inverseJoinColumns {JoinColumn(name role_id, referencedColumnName id)})private Set roles;}/src/main/java/com/example/springbootjpa/entity/Rolepackage com.example.springbootjpa.entity;import lombok.Data;import org.hibernate.annotations.GenericGenerator;import javax.persistence.*;EntityTable(name tb_role)Datapublic class Role {IdGenericGenerator(name idGenerator, strategy uuid)GeneratedValue(generator idGenerator)private String id;Column(name role_name, unique true, nullable false, length 64)private String roleName;}测试Testpublic void findByIdTest() {Optional optional userRepository.findById(40289f0c65674a930165674d54940000);Set roles optional.get().getRoles();System.out.println(optional.get());}不出意外会报Hibernate懒加载异常无法初始化代理类No Sessionorg.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.springbootjpa.entity.User.roles, could not initialize proxy - no Session原因Spring Boot整合JPA后Hibernate的Session就交付给Spring去管理。每次数据库操作后会关闭Session当我们想要用懒加载方式去获得数据的时候原来的Session已经关闭不能获取数据所以会抛出这样的异常。解决方法在application.yml中做如下配置spring:jpa:open-in-view: trueproperties:hibernate:enable_lazy_load_no_trans: true一对多(多对一)/src/main/java/com/example/springbootjpa/entity/Departmentpackage com.example.springbootjpa.entity;import lombok.Data;import org.hibernate.annotations.GenericGenerator;import javax.persistence.*;import java.util.Set;EntityTable(name tb_dept)Datapublic class Department {IdGenericGenerator(name idGenerator, strategy uuid)GeneratedValue(generator idGenerator)private String id;Column(name dept_name, unique true, nullable false, length 64)private String deptName;OneToMany(mappedBy department, cascade CascadeType.ALL, fetch FetchType.LAZY)private Set employees;}/src/main/java/com/example/springbootjpa/entity/Employeepackage com.example.springbootjpa.entity;import lombok.Data;import org.hibernate.annotations.GenericGenerator;import javax.persistence.*;import java.util.UUID;EntityTable(name tb_emp)Datapublic class Employee {IdGenericGenerator(name idGenerator, strategy uuid)GeneratedValue(generator idGenerator)private String id;Column(name emp_name, nullable false, length 64)private String empName;Column(name emp_job, length 64)private String empJob;Column(name dept_id, insertable false, updatable false)private String deptId;ManyToOne(targetEntity Department.class, cascade CascadeType.ALL, fetch FetchType.LAZY)JoinColumn(name dept_id)private Department department;}测试Testpublic void findByIdTest() {Optional optional employeeRepository.findById(93fce66c1ef340fa866d5bd389de3d79);System.out.println(optional.get());}结果报错了...java.lang.StackOverflowErrorat java.base/java.lang.Exception.(Exception.java:102)at java.base/java.lang.ReflectiveOperationException.(ReflectiveOperationException.java:89)at java.base/java.lang.reflect.InvocationTargetException.(InvocationTargetException.java:73)at jdk.internal.reflect.GeneratedConstructorAccessor54.newInstance(Unknown Source)at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:488)at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)at com.mysql.jdbc.PreparedStatement.getInstance(PreparedStatement.java:761)at com.mysql.jdbc.ConnectionImpl.clientPrepareStatement(ConnectionImpl.java:1404)at com.mysql.jdbc.ConnectionImpl.prepareStatement(ConnectionImpl.java:4121)at com.mysql.jdbc.ConnectionImpl.prepareStatement(ConnectionImpl.java:4025)at com.zaxxer.hikari.pool.ProxyConnection.prepareStatement(ProxyConnection.java:318)at com.zaxxer.hikari.pool.HikariProxyConnection.prepareStatement(HikariProxyConnection.java)at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$5.doPrepare(StatementPreparerImpl.java:145)at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:171)at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.prepareQueryStatement(StatementPreparerImpl.java:147)at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.prepareQueryStatement(AbstractLoadPlanBasedLoader.java:226)at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeQueryStatement(AbstractLoadPlanBasedLoader.java:190)at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:121)at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86)at org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer.initialize(AbstractLoadPlanBasedCollectionInitializer.java:87)at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:688)at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:75)at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:2223)at org.hibernate.collection.internal.AbstractPersistentCollection$4.doWork(AbstractPersistentCollection.java:565)at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:247)at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:561)at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:132)at org.hibernate.collection.internal.PersistentSet.hashCode(PersistentSet.java:430)at com.example.springbootjpa.entity.Department.hashCode(Department.java:14)通过日志看sql的输出发现了sql重复执行了好多次。以下我截取了前10条sql记录。Hibernate: select employee0_.id as id1_1_0_, employee0_.dept_id as dept_id2_1_0_, employee0_.emp_job as emp_job3_1_0_, employee0_.emp_name as emp_name4_1_0_ from tb_emp employee0_ where employee0_.id?Hibernate: select department0_.id as id1_0_0_, department0_.dept_name as dept_nam2_0_0_ from tb_dept department0_ where department0_.id?Hibernate: select employees0_.dept_id as dept_id2_1_0_, employees0_.id as id1_1_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id2_1_1_, employees0_.emp_job as emp_job3_1_1_, employees0_.emp_name as emp_name4_1_1_ from tb_emp employees0_ where employees0_.dept_id?Hibernate: select department0_.id as id1_0_0_, department0_.dept_name as dept_nam2_0_0_ from tb_dept department0_ where department0_.id?Hibernate: select employees0_.dept_id as dept_id2_1_0_, employees0_.id as id1_1_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id2_1_1_, employees0_.emp_job as emp_job3_1_1_, employees0_.emp_name as emp_name4_1_1_ from tb_emp employees0_ where employees0_.dept_id?Hibernate: select employees0_.dept_id as dept_id2_1_0_, employees0_.id as id1_1_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id2_1_1_, employees0_.emp_job as emp_job3_1_1_, employees0_.emp_name as emp_name4_1_1_ from tb_emp employees0_ where employees0_.dept_id?Hibernate: select employees0_.dept_id as dept_id2_1_0_, employees0_.id as id1_1_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id2_1_1_, employees0_.emp_job as emp_job3_1_1_, employees0_.emp_name as emp_name4_1_1_ from tb_emp employees0_ where employees0_.dept_id?Hibernate: select employees0_.dept_id as dept_id2_1_0_, employees0_.id as id1_1_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id2_1_1_, employees0_.emp_job as emp_job3_1_1_, employees0_.emp_name as emp_name4_1_1_ from tb_emp employees0_ where employees0_.dept_id?Hibernate: select employees0_.dept_id as dept_id2_1_0_, employees0_.id as id1_1_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id2_1_1_, employees0_.emp_job as emp_job3_1_1_, employees0_.emp_name as emp_name4_1_1_ from tb_emp employees0_ where employees0_.dept_id?Hibernate: select employees0_.dept_id as dept_id2_1_0_, employees0_.id as id1_1_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id2_1_1_, employees0_.emp_job as emp_job3_1_1_, employees0_.emp_name as emp_name4_1_1_ from tb_emp employees0_ where employees0_.dept_id?通过观察发现第一条sql是执行查询Employee的sql第二条sql是执行查询Department的sql第三条sql是执行Department里面所有员工的sql第四条sql是执行查询Department的sql后面所有的sql都是执行查询Department里面所有员工的sql。很明显发生了循环依赖的情况。这是Lombok的Data注解的锅。Lombok的Data注解相当于Getter、Setter、RequiredArgsConstructor、ToString、EqualsAndHashCode这几个注解。我们可以通过反编译看一下Lombok生成的toString()方法// Employeepublic String toString() {return Employee(id getId() , empName getEmpName() , empJob getEmpJob() , deptId getDeptId() , department getDepartment() );}// Departmentpublic String toString() {return Department(id getId() , deptName getDeptName() , employees getEmployees() );}可以发现Lombok为我们生成的toString()方法覆盖了整个类的所有属性现在将Data注解去掉替换为Setter、Getter、EqualsAndHashCode重写toString()方法// DepartmentOverridepublic String toString() {return Department{ id id \ , deptName deptName \ };}// EmployeeOverridepublic String toString() {return Employee{ id id \ , empName empName \ , empJob empJob \ , deptId deptId \ , department department };}再次运行测试用例测试通过以上Employee toString()方法打印的department会触发懒加载最终日志输出的sql如下Hibernate: select employee0_.id as id1_1_0_, employee0_.dept_id as dept_id2_1_0_, employee0_.emp_job as emp_job3_1_0_, employee0_.emp_name as emp_name4_1_0_ from tb_emp employee0_ where employee0_.id?Hibernate: select department0_.id as id1_0_0_, department0_.dept_name as dept_nam2_0_0_ from tb_dept department0_ where department0_.id?Employee{id93fce66c1ef340fa866d5bd389de3d79, empNamejack, empJobhr, deptId0a4fe7234fff42afad34f6a06a8e1821, departmentDepartment{id0a4fe7234fff42afad34f6a06a8e1821, deptName人事部}}再来测试查询DepartmentTestpublic void findByIdTest() {Optional optional departmentRepository.findById(0a4fe7234fff42afad34f6a06a8e1821);Set employees optional.get().getEmployees();Assert.assertNotEquals(0, employees.size());}同样还是报了堆栈溢出错误定位在Department和Employee的hashCode()方法上java.lang.StackOverflowErrorat com.mysql.jdbc.Util.handleNewInstance(Util.java:439)at com.mysql.jdbc.ResultSetImpl.getInstance(ResultSetImpl.java:342)at com.mysql.jdbc.MysqlIO.buildResultSetWithRows(MysqlIO.java:3132)at com.mysql.jdbc.MysqlIO.getResultSet(MysqlIO.java:477)at com.mysql.jdbc.MysqlIO.readResultsForQueryOrUpdate(MysqlIO.java:3115)at com.mysql.jdbc.MysqlIO.readAllResults(MysqlIO.java:2344)at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2739)at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2486)at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1858)at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1966)at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52)at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java)at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:60)at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.getResultSet(AbstractLoadPlanBasedLoader.java:419)at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeQueryStatement(AbstractLoadPlanBasedLoader.java:191)at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:121)at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86)at org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer.initialize(AbstractLoadPlanBasedCollectionInitializer.java:87)at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:688)at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:75)at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:2223)at org.hibernate.collection.internal.AbstractPersistentCollection$4.doWork(AbstractPersistentCollection.java:565)at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:247)at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:561)at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:132)at org.hibernate.collection.internal.PersistentSet.hashCode(PersistentSet.java:430)at com.example.springbootjpa.entity.Department.hashCode(Department.java:17)依旧是Lombok的锅EqualsAndHashCode为我们生成的equals()和hashCode()方法会使用所有属性注意Department中employees是Set集合当我们调用department.getEmployees()时Employee的hashCode()方法会被调用Employee中的hashCode()又依赖于Department的HashCode()方法这样又形成了循环引用...// Departmentpublic int hashCode() {int i 43;String $id getId();int result ($id null ? 43 : $id.hashCode()) 59;String $deptName getDeptName();result (result * 59) ($deptName null ? 43 : $deptName.hashCode());Set $employees getEmployees();int i2 result * 59;if ($employees ! null) {i $employees.hashCode();}return i2 i;}// Employeepublic int hashCode() {int i 43;String $id getId();int result ($id null ? 43 : $id.hashCode()) 59;String $empName getEmpName();result (result * 59) ($empName null ? 43 : $empName.hashCode());String $empJob getEmpJob();result (result * 59) ($empJob null ? 43 : $empJob.hashCode());String $deptId getDeptId();result (result * 59) ($deptId null ? 43 : $deptId.hashCode());Department $department getDepartment();int i2 result * 59;if ($department ! null) {i $department.hashCode();}return i2 i;}自己动手重写equals()和hashCode()方法去掉EqualsAndHashCode注解// DepartmentOverridepublic boolean equals(Object o) {if (this o) return true;if (o null || getClass() ! o.getClass()) return false;Department that (Department) o;return Objects.equals(id, that.id) Objects.equals(deptName, that.deptName);}Overridepublic int hashCode() {return Objects.hash(id, deptName);}// EmployeeOverridepublic boolean equals(Object o) {if (this o) return true;if (o null || getClass() ! o.getClass()) return false;Employee employee (Employee) o;return Objects.equals(id, employee.id) Objects.equals(empName, employee.empName) Objects.equals(empJob, employee.empJob) Objects.equals(deptId, employee.deptId);}Overridepublic int hashCode() {return Objects.hash(id, empName, empJob, deptId);}再次运行测试用例测试通过总结慎用Data注解使用Getter、Setter注解需要时自己重写toString()、equals()以及hashCode()方法审计Auditing参考自官方文档5.9Auditing一般数据库表在设计时都会添加4个审计字段Spring Data Jpa同样支持审计功能。Spring Data提供了CreatedByLastModifiedByCreatedDateLastModifiedDate4个注解来记录表中记录的创建及修改信息。实体类package com.example.springbootjpa.entity;import lombok.Data;import org.hibernate.annotations.GenericGenerator;import org.springframework.data.annotation.CreatedBy;import org.springframework.data.annotation.CreatedDate;import org.springframework.data.annotation.LastModifiedBy;import org.springframework.data.annotation.LastModifiedDate;import org.springframework.data.jpa.domain.support.AuditingEntityListener;import javax.persistence.*;import java.util.Date;import java.util.Set;EntityEntityListeners(AuditingEntityListener.class)Table(name tb_user)Datapublic class User {IdGenericGenerator(name idGenerator, strategy uuid)GeneratedValue(generator idGenerator)private String id;Column(name username, unique true, nullable false, length 64)private String username;Column(name password, nullable false, length 64)private String password;Column(name email, unique true, length 64)private String email;ManyToMany(targetEntity Role.class, cascade CascadeType.ALL, fetch FetchType.LAZY)JoinTable(name tb_user_role, joinColumns {JoinColumn(name user_id, referencedColumnName id)},inverseJoinColumns {JoinColumn(name role_id, referencedColumnName id)})private Set roles;CreatedDateColumn(name created_date, updatable false)private Date createdDate;CreatedByColumn(name created_by, updatable false, length 64)private String createdBy;LastModifiedDateColumn(name updated_date)private Date updatedDate;LastModifiedByColumn(name updated_by, length 64)private String updatedBy;}实体类上还添加了EntityListeners(AuditingEntityListener.class)而AuditingEntityListener是由Spring Data Jpa提供的实现AuditorAware接口光添加了4个审计注解还不够得告诉程序到底是谁在创建和修改表记录/src/main/java/com/example/springbootjpa/auditing/AuditorAwareImplpackage com.example.springbootjpa.auditing;import org.springframework.data.domain.AuditorAware;import org.springframework.stereotype.Component;import java.util.Optional;Componentpublic class AuditorAwareImpl implements AuditorAware {Overridepublic Optional getCurrentAuditor() {return Optional.of(admin);}}这里简单的返回了一个admin字符串来代表当前用户名启用Jpa审计功能在Spring Boot启动类上添加EnableJpaAuditing注解用于启用Jpa的审计功能package com.example.springbootjpa;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.data.jpa.repository.config.EnableJpaAuditing;SpringBootApplicationEnableJpaAuditingpublic class SpringBootJpaApplication {public static void main(String[] args) {SpringApplication.run(SpringBootJpaApplication.class, args);}}
http://wiki.neutronadmin.com/news/340002/

相关文章:

  • 如何使用天翼云主机建设网站网站建设v5star
  • 申请个人主页网站地址企业的网站内容
  • 学校网站开发说明书文档可以做热图的在线网站
  • 建立网站模板二环建设部网站
  • 集团网站源码一份完整的个人简历模板
  • v9双语版网站怎么做山西设计网站建设
  • 电子商务网站建设作业案例网络规划师多少分合格
  • 安徽省住房和城乡建设厅网站6镇江营销型建站公叿
  • 多城市网站开发哪些网站可以做设计软件
  • 鞍山工程建设信息网站软件定制开发服务收费多少
  • 营销网站建设专业团队在线服务wordpress怎么搜索别人的文章
  • 车票网站模板湛江做网站咨询电话
  • 常州城乡建设局网站首页制作作业平台网站的设计
  • 手机网站系统什么网站有高端定制案例
  • 建设银行租房平台网站6商品展示网站模板源码
  • 网站文章多久收录宝塔wordpress恢复出错
  • 做的王者荣耀钓鱼网站网站提交工具
  • 建设一个自己的网站网站做全局搜索
  • 不用代码的网站建设2021年uc秒懂网址
  • 傻瓜式php网站开发wordpress样式切换功能
  • 网站链接的常见形式刚刚北京发生大事了
  • 牛仔裤网站设计高端网站建设哪家好
  • 东莞物流网站设计公司网站中的自助报价系统
  • 手表网站推荐做网站怎么学
  • 重庆网站建设去迅法网新站整站排名优化火速公司
  • 制作网站的软件叫什么外贸模版网站
  • 网站开发平台开发公司企业官网网站
  • 全球50个大网站开发语言wordpress add page
  • 免费做名片儿的网站综合办公oa系统
  • 建设网站建设什么征琴wordpress高并发