山一程--软件开发--数据核心--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 + '\'' +
                '}';
    }
}
Course entity
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();
}
Repository
public interface CourseStatusUpdate {
    void updateStatus();
}
interface define
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 {
}
config
@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();

    }
  }
}
main 

  


附  非正式使用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;
    }
config

解决方案:

 

@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);
    }
}
JpaCourseRepository
@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();
    }
}
config


Spring 事务管理代理,异步代理的顺序问题,

如果事务在异步之前执行,那么创建异步方法相关的操作将包含在事务管理中.而且线程绑定的事务可能无法正常工作.

解决办法:两个注解的 order 特性, order

《Spring web高级编程》 p558


 

附:

注意,但实际中可能不配置如下的两个 bean。

 

posted @ 2023-04-15 20:57  君子之行  阅读(68)  评论(0)    收藏  举报