山一程--软件开发--数据核心--Spring JPA + Hibernate 配置相关
目的:使用Spring data JPA 及其实现 Hibernate 构建应用程序数据领域核心,非databse
Spring data JPA 规范,简化了DAO 及常用 CRUD 操作。
JTA 是一个事务处理监控器,在多个资源间协调事务,例如数据库和应用消息传递系统 (JMS, AMQP)
基于 JPA 规范和其实现 Hibernate
依赖:spring-boot-starter-jdbc
此依赖引入 spring-jdbc, spring-tx, 默认连接池的 HikariCP
<!-- 将包含 hibernate-core && spring-orm -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
#-jdbc- spring.datasource.url=jdbc:mysql://localhost:3306/PRACTICE_SPRING_HIBERNATE?userUnicode=true&characterEncoding=utf-8&serverTimezone=GMT spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.username=PRACTICE_SPRING_HIBERNATE spring.datasource.password=admin #-HikariCP- #客户端等待连接池分配连接的最大毫秒数,默认30秒 spring.datasource.hikari.connection-timeout = 30000 #池中维护的最少空闲连接数,默认为10个 spring.datasource.hikari.minimum-idle = 2 #连接池能保留的最大连接数,默认为10个 spring.datasource.hikari.maximum-pool-size=10 #允许连接在池中空闲的最大毫秒数,默认为10分钟 spring.datasource.hikari.idle-timeout=600000 #在日志中记录相关信息以指示可能的连接泄露之前,可从池中取出一个连接的毫秒数,默认为0(不允许) spring.datasource.hikari.leak-detection-threshold=20000 #-jpa hibernate- #获取数据大小 spring.jpa.properties.hibernate.jdbc.fetch_size=250 #批处理大小 spring.jpa.properties.hibernate.jdbc.batch_size=50 #目标数据库名称,可指定方言 spring.jpa.database-platform=org.hibernate.dialect.MySQL8InnoDBDialect spring.jpa.show-sql=true
Spring boot 检测到 Hibernate 时,使用前面的DataSource 自动配置一个 EntityManagerFactory 实例
2023-04-16 10:46:59
大量复杂的领域模型, 解决方案:Spring ORM ,Hibernate
实体类/持久化类 : 代表真实世界的实体,其实例会被持久化到数据库中, 实体类需要一个不带参数的默认构造方法.
每个实体类,必须要定义一个标识符属性来唯一标识一个实体。
最佳实践是定义一个自动生成的标识符,因为它没有业务含义,因此在任何情况下都不会被修改。
这个标识符会被 ORM 框架用来确定实体的状态:
如果标识符为 null, 则该实体就会被看作是新创建且尚未保存的。该实体被持久化时,执行 insert sql 语句.
如果表示符不为null, 则使用更新语句.


应该为标识符选择原生包装类型, 为 java.lang.Integer 和 java.lang.Long;
不同数据访问策略核心
| 概念 | JDBC | JPA | Hibernate |
| 资源 | Connection | EntityManager | Session |
| 资源工厂 | DataSource | EntityManagerFactory | SessionFactory |
| 异常 | SQLException | PersistenceException | HibernateException |
所有异常都是RuntimeException子类,并不会强制对其捕获和处理.
实践:
CREATE TABLE COURSE( ID bigint not null auto_increment primary key, TITLE varchar(60) not null, CONTENT varchar(100) not null, BEGIN_DATE date not null, END_DATE date not null, FEE int not null CHECK (FEE > 10), STATUS varchar(10) not null, unique UQ_TITLE ( title ) )ENGINE = InnoDB;
核心:集成自 Spring data JPA 接口即可,



JpaRepository 和 PagingAndSortingRepository 和 CrudRepository 的 18个方法.
自定义查询方法:

Spring data 允许的在方法名中的 4个动词:find , get, read 查询并返回对象, count 返沪查询数量.
要查询的对象是通过如何参数化 JpaRepository 接口来确定的,而不是方法名称中的主题.
主题前面加 Distinct,在生成查询的时候确保结果集中不包含重复记录.
断言:指定了限制结果集的属性.
一个或多个限制结果的条件. 每个条件必须引用一个属性,且还可以指定一种比较操作.
如果省略比较操作符的话,暗指是相等比较操作.
- IsAfter, After, IsGreaterThan, GreaterThan, IsGreaterThanEqual, GreaterThanEqual.
- IsBefore, Before, IsLessThan, LessThan, IsLessThan, LessThanEqual
- IsBetween, Between.
- IsNull, Null, IsNotNull, NotNull
- IsIn, In, IsNotIn, NotIn
- IsStartingWith, StartingWith, StartsWith
- IsEndingWith, EndingWith, EndsWith
- IsContaining, Containing, Contains
- IsLike, Like, IsNotLink, NotLike
- IsTrue, True, IsFalse, False
- Is, Equals, IsNot, Not
要对比的属性值就是方法的参数.

String 类型的属性,可带上IgnoringCase 或 IgnoresCase. 或者统一 AllIgnoresCase

List<Pet> findPetsByBreadIn ( List<String> bread )
int countProductsByDiscountingedTrue()
List<Order> findByShippingDateBetween (Date start, Date end)
小型 DSL
@Query 注解
限制:仅限于单个JPA查询。

混合自定义功能.
Spring data Jpa 为 Repository 接口生成实现的时候,还会查找名字与接口相同,且添加了 Impl后缀的一个类,会把其方法合并。



附上:完整实现

package road.tech.base.springdatahibernate.doamin.model.entity.course; import javax.persistence.*; import java.util.Date; @Entity @Table(name="COURSE") public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="ID") private Long id; @Column(name="TITLE",length=60,nullable=false) private String title; @Column(name="CONTENT",length=100,nullable = false) private String content; @Column(name="BEGIN_DATE") private Date beginDate; @Column(name="END_DATE") private Date endDate; @Column(name="FEE") private int fee; @Column(name="STATUS") private String status; public Course(){} public Long getId() { return id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public void setBeginDate(Date beginDate) { this.beginDate = beginDate; } public void setEndDate(Date endDate) { this.endDate = endDate; } public void setFee(int fee) { this.fee = fee; } public Date getBeginDate() { return beginDate; } public Date getEndDate() { return endDate; } public int getFee() { return fee; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @Override public String toString() { return "Course{" + "id=" + id + ", title='" + title + '\'' + ", content='" + content + '\'' + ", beginDate=" + beginDate + ", endDate=" + endDate + ", fee=" + fee + ", status='" + status + '\'' + '}'; } }
public interface CourseRepository extends JpaRepository<Course,Long>,CourseStatusUpdate{ List<Course> readByTitleOrContent(String title,String content); List<Course> readByTitleOrContentOrderByTitleAsc(String title,String content); @Query("select c from Course c where c.content like '%like_query'") List<Course> findAllLikeContentCourses(); }
public interface CourseStatusUpdate { void updateStatus(); }
package road.tech.base.springdatahibernate.port.adapter.persistence; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @EnableTransactionManagement @EnableJpaRepositories(basePackages = { "road.tech.base.springdatahibernate.doamin.model.entity.course", "road.tech.base.springdatahibernate.port.adapter.persistence"}, repositoryImplementationPostfix = "Impl") public class CourseRepositoryConfig { }
@SpringBootApplication public class SpringDataHibernateApplication { public static void main(String[] args) { SpringApplication.run(SpringDataHibernateApplication.class, args); } @Component class app implements ApplicationRunner{ @Autowired CourseRepository courseRepository; @Override public void run(ApplicationArguments args) throws Exception { courseRepository.updateStatus(); } } }
附 非正式使用Jpa Hibernate:
无论何时使用 @EnableTransactionManagement 都必须要提供一个PlatformTransactionManager 的默认代理.
对于 JPA 资源,应使用 JpaTransactionManager, 其构造函数绑定了一个 EntityManagerFactory. 应使用LocalContainerEntityManagerFacotryBean
构造
JPA : 在Spring 应用上下文中将实体管理器工厂 entity manager factory 按照bean 的形式来配置.
application-managed 和 container-managed ,都实现了同一个 EntityManager 接口,区别在于EntityManager的创建和管理方式.
container-managed 可使用注解配置。无需 persistence.xml 且在META-INF 目录下.

以下示例是为了说明 EntityManagerFacotry 不是线程安全。不要使用如下示例:
@Configuration @EnableTransactionManagement @EnableJpaRepositories @EnableAutoConfiguration @EntityScan(basePackages = { "org.tech.road.springjpahibernatepractice1.domain.model", "org.tech.road.springjpahibernatepractice1.port.adapter.persistence" }) public class JpaConfig { @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory( DataSource dataSource, JpaVendorAdapter jpaVendorAdapter){ LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean(); emfb.setDataSource(dataSource); emfb.setJpaVendorAdapter(jpaVendorAdapter); emfb.setPackagesToScan("org.tech.road.springjpahibernatepractice1.domain.model"); return emfb; } @Bean public JpaVendorAdapter jpaVendorAdapter(){ HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); return adapter; }

解决方案:
@Repository("courseRepository")
public class JpaCourseRepository implements CourseRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
@Transactional
public void addCourse(Course course) {
entityManager.persist(course);
}
@Override
@Transactional(readOnly = true)
public Course getCourseById(long id) {
return entityManager.find(Course.class,id);
}
@Override
@Transactional
public void saveCourse(Course course) {
entityManager.merge(course);
}
}
@Configuration @EnableTransactionManagement @EnableJpaRepositories @EnableAutoConfiguration @EntityScan(basePackages = { "org.tech.road.springjpahibernatepractice1.domain.model", "org.tech.road.springjpahibernatepractice1.port.adapter.persistence" }) public class JpaConfig { @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory( DataSource dataSource, JpaVendorAdapter jpaVendorAdapter){ LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean(); emfb.setDataSource(dataSource); emfb.setJpaVendorAdapter(jpaVendorAdapter); emfb.setPackagesToScan("org.tech.road.springjpahibernatepractice1.domain.model"); return emfb; } @Bean public JpaVendorAdapter jpaVendorAdapter(){ HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); return adapter; } @Bean public PersistenceAnnotationBeanPostProcessor persistenceAnnotationBeanPostProcessor(){ return new PersistenceAnnotationBeanPostProcessor(); } @Bean public BeanPostProcessor persistenceTranslation(){ return new PersistenceExceptionTranslationPostProcessor(); } }



Spring 事务管理代理,异步代理的顺序问题,
如果事务在异步之前执行,那么创建异步方法相关的操作将包含在事务管理中.而且线程绑定的事务可能无法正常工作.
解决办法:两个注解的 order 特性, order
《Spring web高级编程》 p558
附:
注意,但实际中可能不配置如下的两个 bean。


浙公网安备 33010602011771号