MySQL 5.7 Spring JPA 事务管理不起作用

Posted

技术标签:

【中文标题】MySQL 5.7 Spring JPA 事务管理不起作用【英文标题】:MySQL 5.7 Spring JPA transaction management not worknig 【发布时间】:2021-10-15 09:54:22 【问题描述】:

mysql 版本:5.7.35

正在使用的 MySQL JDBC 驱动程序:mysql-connector-java-8.0.25.jar:8.0.25

Spring Boot:2.5.0

我有两个数据库表:

create table first
(
    id bigint auto_increment
        primary key,
    item_id int not null,
    element varchar(25) not null
    version int not null,
    unique (item_id, element)
) ENGINE=InnoDB;

create table second
(
    id bigint auto_increment
        primary key,
    item_id int not null,
    element varchar(25) not null
    version int not null,
    unique(item_id, element)
) ENGINE=InnoDB;

我们不用担心为什么我有 2 张外观完全相同的桌子。我在这里简化问题陈述。实际上,业务逻辑需要 2 个具有多列的独立表。

在我的 Spring Boot 2 应用程序中,我必须配置 2 个数据源:Vertica(用于读取数据)和MySQL 5.7.35 用于持久化。

相关的MySQLDataSourceConfig.java(我也有类似的课程VerticaDataSourceConfig.java,这里不展示)

@Configuration
@ConfigurationProperties("mysql.datasource")
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "mysqlEntityManagerFactory",
        transactionManagerRef = "mysqlTransactionManager",
        basePackages =  "com.mysql.package.repository" 
)
public class MySQLDataSourceConfig extends HikariConfig 
    public final static String PERSISTENCE_UNIT_NAME = "mysql";
    public final static String PACKAGES_TO_SCAN = "com.mysql.package.entity";

    @Autowired private Environment env;

    @Bean
    public HikariDataSource mysqlDataSource() 
        return new HikariDataSource(this);
    

    @Bean
    public LocalContainerEntityManagerFactoryBean mysqlEntityManagerFactory(
            final HikariDataSource mysqlDataSource) 

        return new LocalContainerEntityManagerFactoryBean() 
            setDataSource(mysqlDataSource);
            setPersistenceProviderClass(HibernatePersistenceProvider.class);
            setPersistenceUnitName(PERSISTENCE_UNIT_NAME);
            setPackagesToScan(PACKAGES_TO_SCAN);

            HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
            adapter.setDatabasePlatform("org.hibernate.dialect.MySQL57Dialect");
            adapter.setDatabase(Database.MYSQL);
            adapter.setShowSql(true);
            setJpaVendorAdapter(adapter);

            Properties jpaProperties = new Properties();
            jpaProperties.put("hibernate.ddl-auto", env.getProperty("mysql.jpa.hibernate.ddl-auto"));
            jpaProperties.put("hibernate.show-sql", env.getProperty("mysql.jpa.hibernate.show-sql"));
            jpaProperties.put("hibernate.format_sql", env.getProperty("mysql.jpa.hibernate.format_sql"));
            //jpaProperties.put("hibernate.dialect", env.getProperty("mysql.jpa.properties.hibernate.dialect"));

            setJpaProperties(jpaProperties);

            afterPropertiesSet();
        ;
    

    @Bean
    public PlatformTransactionManager mysqlTransactionManager(EntityManagerFactory mysqlEntityManagerFactory) 
        return new JpaTransactionManager(mysqlEntityManagerFactory);
    

application.yaml 用于 MySQL 特定属性(Vertica 的类似属性未在此处显示:

spring:
  autoconfigure:
    exclude: >
      org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,
      org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,
      org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration

mysql:
  datasource:
    jdbc-url: jdbc:mysql://$MYSQL_HOST:localhost:3306/mydb
    username: root
    password: mypassword
    driver-class-name: com.mysql.jdbc.Driver
    hikari:
      connectionTimeout: 30000
      idleTimeout: 30000
      maxLifetime: 2000000
      maximumPoolSize: 20
      minimumIdle: 5
      poolName: mysql-db-pool
      #username: $DB_USER
      #password: $DB_PASSWORD
  jpa:
    hibernate:
      ddl-auto: none
      format_sql: true
      show-sql: true
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
      naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
    properties:
      hibernate:
        #dialect: org.hibernate.dialect.MySQL57Dialect
        dialect: org.hibernate.dialect.MySQLInnoDBDialect
          #storage_engine: innodb
        #default_schema: hotspot

这里是存储库类:

MySQLFirstRepository.java

@Repository
public interface MySQLFirstRepository extends CrudRepository<First, BigInteger> 

MySQLSecondRepository.java

@Repository
public interface MySQLSecondRepository extends CrudRepository<Second, BigInteger> 

您也可以假设我在运行应用程序时提供了一个 JVM 属性:

-Dhibernate.dialect.storage_engine=innodb

以下是实体类:

First.java

@Entity
@Table(name = "first")
@Getter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class First 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private BigInteger id;

    @Column(name = "item_id")
    private BigInteger itemId;

    @Column(name = "element")
    private String element;

    @Column(name = "version")
    @Version
    private int version;


Second.java

@Entity
@Table(name = "second")
@Getter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Second 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private BigInteger id;

    @Column(name = "item_id")
    private BigInteger itemId;

    @Column(name = "element")
    private String element;

    @Column(name = "version")
    @Version
    private int version;


问题:

在我的服务类中,我有这样的东西:

@Transactional(
            transactionManager = "mysqlTransactionManager",
            propagation = Propagation.REQUIRED,
            rollbackFor = Exception.class,
            isolation = Isolation.DEFAULT)
    public void persist(List<First> firstList, List<Second> secondList) 
        firstRepo.saveAll(firstList);
        secondRepo.saveAll(secondList);
    

secondRepo.saveAll(secondList) 方法抛出以下异常:

org.springframework.orm.jpa.JpaSystemException: org.hibernate.exception.ConstraintViolationException: could not execute statement; nested exception is javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement

尽管如此,第一个表被填充并且没有回滚。我读到这可能是由于 MySQL 中基于MyISAMInnoDB 的表,尝试了几件事(如您在上面代码片段中的注释行中所见),尝试设置前面提到的 JVM 系统属性,尝试将ENGINE=InnoDB 放入我的CREATE TABLE 语句中,但似乎没有任何效果。

请问可以帮忙吗?如果在发布之前先尝试解决方案,我将不胜感激,因为如上所述,我已经尝试了技术论坛上几乎所有可用的方法。

请注意,标准 Spring JPA 属性无法配置,因为我必须定义自己的 DataSourceConfig(因为有多个数据源),所以我必须使用 Hibernate 属性,正如您在上面的 MySQLDataSourceConfig.java 中看到的那样。

【问题讨论】:

【参考方案1】:

我最终能够自己解决这个问题。在此处发布详细信息,以便其他人受益。

首先要了解一些事实:

    MySQL 5.7 以上,默认存储引擎为 InnoDB (https://dev.mysql.com/doc/refman/5.7/en/innodb-introduction.html) In MySQL 5.7, InnoDB is the default MySQL storage engine. Unless you have configured a different default storage engine, issuing a CREATE TABLE statement without an ENGINE clause creates an InnoDB table.

    不需要使用-Dhibernate.dialect.storage_engine=innodb命令行系统属性。

    MySQL*InnoDBDialect 类已弃用

    要使用的方言是:org.hibernate.dialect.MySQL57Dialect

解决方案:

Spring @Transactional 似乎没有被默认的 Spring Proxy AOP 正确拦截。解决方案是使用 Aspect J (Does Spring @Transactional attribute work on a private method?)

重构代码以确保@Transactional 方法的调用者驻留在不同的类中。

这是一个例子(只是一个例子来说明我的意思,代码应该被适当地重构并且应该使用适当的编码和设计原则)

来电代码:

@Service
public class MyService 
    @Autowired private Persister persister;

    public void doSomething() 
        // do something to get a list of entity First and Second
        persister.persist(firstEntityList, secondEntityList);
    

被调用者:

public class Persister 
    @Autowired private MySQLFirstRepository firstRepo;
    @Autowired private MySQLSecondRepository secondRepo;

    @Transactional("mysqlTransactionManager") // use other attributes to suit your needs; see some options above in the question
    public void persist(List<First> firstEntityList, List<Second> secondEntityList) 
        firstRepo.saveAll(firstEntityList);
        secondRepo.saveAll(secondEntityList);
    

【讨论】:

以上是关于MySQL 5.7 Spring JPA 事务管理不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot + Spring Data JPA + 事务无法正常工作

Spring Boot - Mysql Driver - JPA - 在服务器运行发布请求很长时间后显示无法打开 JPA EntityManager 进行事务处理

两小时上手Spring Boot (开发红包程序———通过JPA 连接Mysql ,事务等内容) 遇到的问题

springboot用jpa生成表,没有外键

具有 Mysql JSON 类型的 Spring Data

文档未保存在 Spring jpa 文档管理器应用程序中