MySQL 5.7 Spring JPA 事务管理不工作
MySQL 5.7 Spring JPA transaction management not worknig
MySQL版本:5.7.35
MySQL JDBC 正在使用的驱动程序:mysql-connector-java-8.0.25.jar:8.0.25
Spring 开机:2.5.0
我有两个数据库tables:
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 个看起来完全相似的 table。我在这里简化问题陈述。实际上,业务逻辑需要 2 个独立的 table 和多个列。
在我的 Spring Boot 2 应用程序中,我必须配置 2 个数据源:Vertica
(用于读取数据)和 MySQL 5.7.35
用于持久化。
相关的MySQLDataSourceConfig.java
(我也有类似的classVerticaDataSourceConfig.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
这里是存储库 classes:
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
这是实体 classes:
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;
}
问题:
在我的服务class中,我有这样的事情:
@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
尽管如此,第一个 table 会被填充并且不会回滚。我读到这可能是由于 MyISAM
vs InnoDB
基于 tables in MySQL,尝试了几件事(正如您在代码的注释行中看到的那样上面的片段),尝试设置前面提到的 JVM 系统 属性,尝试将 ENGINE=InnoDB
放入我的 CREATE TABLE
语句中,但似乎没有任何效果。
请问有什么帮助吗?如果在发布之前先尝试该解决方案,我将不胜感激,因为如前所述,我已经尝试了技术论坛上几乎所有可用的方法。
注意无法配置标准 Spring JPA 属性,因为我必须定义自己的 DataSourceConfig(因为有多个数据源),因此我必须使用 Hibernate 属性,如您在 MySQLDataSourceConfig.java
以上。
我最终能够自己解决这个问题。在此处发布详细信息以便其他人受益。
先了解一些事实:
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
class已弃用
要使用的方言是:org.hibernate.dialect.MySQL57Dialect
解决方案:
似乎 Spring @Transactional 没有被默认的 Spring 代理 AOP 正确拦截。解决方案是使用 Aspect J (Does Spring @Transactional attribute work on a private method?)
或
重构代码以确保 @Transactional 方法的调用者驻留在不同的 class.
这里有一个例子(只是一个例子来说明我的意思,应该适当地重构代码并且应该使用适当的编码和设计原则)
来电号码:
@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.35
MySQL JDBC 正在使用的驱动程序:mysql-connector-java-8.0.25.jar:8.0.25
Spring 开机:2.5.0
我有两个数据库tables:
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 个看起来完全相似的 table。我在这里简化问题陈述。实际上,业务逻辑需要 2 个独立的 table 和多个列。
在我的 Spring Boot 2 应用程序中,我必须配置 2 个数据源:Vertica
(用于读取数据)和 MySQL 5.7.35
用于持久化。
相关的MySQLDataSourceConfig.java
(我也有类似的classVerticaDataSourceConfig.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
这里是存储库 classes:
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
这是实体 classes:
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;
}
问题:
在我的服务class中,我有这样的事情:
@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
尽管如此,第一个 table 会被填充并且不会回滚。我读到这可能是由于 MyISAM
vs InnoDB
基于 tables in MySQL,尝试了几件事(正如您在代码的注释行中看到的那样上面的片段),尝试设置前面提到的 JVM 系统 属性,尝试将 ENGINE=InnoDB
放入我的 CREATE TABLE
语句中,但似乎没有任何效果。
请问有什么帮助吗?如果在发布之前先尝试该解决方案,我将不胜感激,因为如前所述,我已经尝试了技术论坛上几乎所有可用的方法。
注意无法配置标准 Spring JPA 属性,因为我必须定义自己的 DataSourceConfig(因为有多个数据源),因此我必须使用 Hibernate 属性,如您在 MySQLDataSourceConfig.java
以上。
我最终能够自己解决这个问题。在此处发布详细信息以便其他人受益。
先了解一些事实:
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
class已弃用要使用的方言是:
org.hibernate.dialect.MySQL57Dialect
解决方案:
似乎 Spring @Transactional 没有被默认的 Spring 代理 AOP 正确拦截。解决方案是使用 Aspect J (Does Spring @Transactional attribute work on a private method?)
或
重构代码以确保 @Transactional 方法的调用者驻留在不同的 class.
这里有一个例子(只是一个例子来说明我的意思,应该适当地重构代码并且应该使用适当的编码和设计原则)
来电号码:
@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);
}
}