引自免费微信小程序:皆我百晓生
根据您提供的信息,看起来像是在使用Spring框架的@Transactional
注解来处理数据库事务,但是似乎存在并发问题导致数据不一致。首先,我们需要检查事务隔离级别是否设置正确,并且确保在插入操作之间没有其他并发修改。
Spring默认的事务隔离级别是READ_COMMITTED
,这可能导致在高并发场景下的幻读(Phantom Read)或不可重复读(Non-repeatable read)。在这种情况下,建议将事务隔离级别设置为SERIALIZABLE
,虽然这可能会降低性能,但能确保数据一致性。
以下是修改事务配置的示例:
在您的Spring Boot应用的application.properties
文件中添加以下内容:
spring.jpa.properties.hibernate.default_batch_fetch_size=1
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.open-in-view=false
spring.datasource.isolation-level-read-committed=true
或者,如果您使用的是Java配置类,可以这样设置:
@Configuration
@EnableTransactionManagement
public class AppConfig {
@Bean
public DataSource dataSource() {
// Your data source configuration here
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
HibernateTransactionManager htm = new HibernateTransactionManager(entityManagerFactory);
htm.setHibernateProperties(hibernateProperties());
return htm;
}
Properties hibernateProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.default_batch_fetch_size", "1");
properties.setProperty("hibernate.order_inserts", "true");
properties.setProperty("hibernate.order_updates", "true");
properties.setProperty("hibernate.jdbc.batch_size", "50");
properties.setProperty("hibernate.isolation", "Serializable");
return properties;
}
}
接下来,确认一下plateInfo()
和parkVehInfo()
方法中的逻辑是否有并发风险。从目前的代码来看,它们各自独立地创建了新的实体对象并插入到数据库,看起来应该是安全的,除非有其他地方也访问这些数据。
为了进一步排查,您可以考虑以下几个步骤:
- 检查是否有其他地方并发修改这两张表的数据。
- 在插入操作前后打印日志,查看是否所有插入操作都成功执行,例如:
@Override
@Transactional(rollbackFor = Exception.class)
public void insertData(TblEventPlateInfo tblEventPlateInfo) {
log.info("Starting to insert data...");
plateInfo(tblEventPlateInfo);
log.info("Inserted into plate table successfully.");
parkVehInfo(tblEventPlateInfo);
log.info("Inserted into parking vehicle table successfully.");
log.info("Finished inserting data.");
}
- 如果仍然无法确定问题所在,您可以尝试在
insertData()
方法中使用显式保存点(Savepoint),以便在其中一个操作失败时回滚到特定点。但这可能增加复杂性,因为您需要捕获异常并在回滚后处理它。
@Override
@Transactional(rollbackFor = Exception.class)
public void insertData(TblEventPlateInfo tblEventPlateInfo) {
try (Connection conn = yourEntityManager.unwrap(Connection.class)) {
conn.setAutoCommit(false);
// ... 之前的代码
String savePointName = "save_point";
Savepoint savePoint = conn.setSavepoint(savePointName);
plateInfo(conn, tblEventPlateInfo); // Pass the connection explicitly
if (!conn.getAutoCommit()) {
conn.commit(); // Commit after each operation
}
log.info("Inserted into plate table successfully.");
parkVehInfo(conn, tblEventPlateInfo); // Pass the connection explicitly
if (!conn.getAutoCommit()) {
conn.commit(); // Commit after each operation
}
log.info("Inserted into parking vehicle table successfully.");
log.info("Finished inserting data.");
} catch (Exception e) {
log.error("Error occurred during insertion.", e);
try {
if (!conn.getAutoCommit()) {
conn.rollback(savePoint); // Rollback to the save point if there's an exception
}
} catch (SQLException sqlEx) {
log.error("Failed to rollback transaction.", sqlEx);
}
} finally {
try {
if (!conn.getAutoCommit()) {
conn.setAutoCommit(true);
}
} catch (SQLException ex) {
log.error("Failed to set auto-commit back to true.", ex);
}
}
}
private void plateInfo(Connection conn, TblEventPlateInfo tblEventPlateInfo) {
// Use the passed connection in the query
tblEventPlateInfo.setId(remoteIdProducerService.nextId());
tblEventPlateInfoMapper.insert(conn, tblEventPlateInfo);
}
private void parkVehInfo(Connection conn, TblEventPlateInfo tblEventPlateInfo) {
// Use the passed connection in the query
TblParkingVehicleInfo info = new TblParkingVehicleInfo();
// ...
tblParkingVehicleInfoMapper.insert(conn, info);
}
请尝试上述建议,如果还有问题,请提供更详细的日志输出,以便更好地分析问题。