【问题标题】:Spring+Hibernate Integration: Transaction Manager doesn't work using @TransactionalSpring+Hibernate 集成:事务管理器无法使用 @Transactional
【发布时间】:2014-06-18 11:02:39
【问题描述】:

Spring 配置是在代码中使用注释而不是 XML 文件完成的。我试图通过休眠查询一些数据并在 ORACLE 数据库中创建新列。我的问题是hibernate只生成SELECT查询,当我使用sessionFactory.getCurrentSession().save()时,hibernate不会生成INSERT查询。我认为这可能是一个交易问题,但找不到哪里出了问题。我将把代码放在下面,任何帮助将不胜感激。

这就是主要的配置类:

@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableTransactionManagement
@PropertySource({ "file:src/main/resources/Config/database.properties" })
public class QCWordImportExportTool {

    @Autowired
    private Environment env;

    @Autowired
    private WietHibernateInterceptor wietHibernateInterceptor;

    /**
     * main, says it all! :)
     * 
     * @param args
     */
    public static void main(String[] args) {
        SpringApplication.run(QCWordImportExportTool.class, args);
    }

    @Bean
    MultipartConfigElement multipartConfigElement() {
        MultiPartConfigFactory factory = new MultiPartConfigFactory();
        factory.setMaxFileSize("10MB");
        factory.setMaxRequestSize("1024KB");
        return factory.createMultipartConfig();
    }

    @Bean
    public LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(restDataSource());
        sessionFactory.setPackagesToScan(new String[] { "com.ciena.prism.almtools.wiet" });
        sessionFactory.setHibernateProperties(hibernateProperties());
        sessionFactory.setEntityInterceptor(this.wietHibernateInterceptor);

        return sessionFactory;
    }

    @Bean
    public DataSource restDataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
        dataSource.setUrl(env.getProperty("jdbc.url"));
        dataSource.setUsername(env.getProperty("jdbc.username"));
        dataSource.setPassword(env.getProperty("jdbc.password"));

        return dataSource;
    }

    @Bean
    public HibernateTransactionManager transactionManager() {
        HibernateTransactionManager transactionManager = new HibernateTransactionManager();
        transactionManager.setSessionFactory(sessionFactory().getObject());

        return transactionManager;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }

    Properties hibernateProperties() {
        return new Properties() {
            private static final long serialVersionUID = 1L;

            {
                setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
                setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
                setProperty("hibernate.globally_quoted_identifiers",
                            env.getProperty("hibernate.globally_quoted_identifiers"));
                setProperty("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
                setProperty("hibernate.format_sql", env.getProperty("hibernate.format_sql"));
            }
        };
    }
}

Service 类是一个接口,我将使用 main 方法发布 Service Impl 类:

@Service
@Transactional(readOnly = true)
public class ImportExportManagerImpl implements ImportExportManager {
    private TestFacade testFacade;
    private TestFolderFacade testFolderFacade;
    private UserManager userManager;

    @Autowired
    SessionFactory sessionFactory;

    @Autowired
    RequirementCoverageDAO requirementCoverageDao;

    @Autowired
    RequirementDAO requirementDao;

    @Autowired
    WietHibernateInterceptor wietHibernateInterceptor;

    @Autowired
    public ImportExportManagerImpl(TestFacade testFacade, TestFolderFacade testFolderFacade,
                                   UserManager userManager) {
        this.testFacade = testFacade;
        this.testFolderFacade = testFolderFacade;
        this.userManager = userManager;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.ciena.prism.almtools.wiet.managers.ImportExportManager#importTestCases(java.lang.String,
     * java.lang.String, java.util.List)
     */
    @Override
    @Transactional(readOnly = false)
    public void importTestCases(String domain, String project, List<TestCase> testCases)
            throws RequestFailureException, RESTAPIException, InvalidDataException {
        System.out.println("Start to import...");

        setDBSchema(domain, project);

        for (TestCase testCase : testCases) {
            TestFolder testFolder = retrieveTestFolderFromPath(domain, project, testCase.getFolderPath());
            Test test = new Test(testCase, testFolder);
            ALMEntity almEntity = new ALMEntity(test);

            Test existingTest = getExistingTest(domain, project, test);
            if (existingTest == null) {
                existingTest = new Test(testFacade.createEntity(domain, project, almEntity));
            } else {
                testFacade.updateEntity(domain, project, existingTest.getId(), almEntity);
            }
            System.out.println(existingTest.getName());

            /* Create Requirement_Coverage using test and doors_object_ids */
            List<String> doors_object_ids = testCase.getDoors_object_ids();

            for (String doors_object_id : doors_object_ids) {
                List<Requirement> requirementList = requirementDao.findAllFromDoorsobjectid(doors_object_id);

                if (requirementList != null && !requirementList.isEmpty()) {
                    System.out.println("*************Requirement:" + doors_object_id + " not null");
                    /* check if the coverage already exist */
                    Requirement requirement = requirementList.get(0);
                    List<RequirementCoverage> requirementCoverageList = requirementCoverageDao
                            .findAllFromTestIdReqId(Integer.parseInt(existingTest.getId()),
                                                    requirement.getReqId());
                    if (requirementCoverageList == null || requirementCoverageList.isEmpty()) {
                        System.out.println("**************Creating new requirement coverage");

                        /* create a new Requirement Coverage Object */
                        RequirementCoverage requirementCoverage = new RequirementCoverage();
                        requirementCoverage.setRequirement(requirement);
                        requirementCoverage.setEntityId(Integer.parseInt(existingTest.getId()));
                        requirementCoverage.setEntityType("TEST");
                        requirementCoverageDao.create(requirementCoverage);
                        System.out.println("*********assigned DB id: " + requirementCoverage.getId());
                    }

                } else {
                    throw new InvalidDataException("Requirement Management Tool Id : " + doors_object_id
                            + " doesn't exist in QC");
                }
            }
        }

    }
}

这是 DAO impl 类:

@Repository
public class RequirementCoverageDAOImpl implements RequirementCoverageDAO {

    @Autowired
    private SessionFactory sessionFactory;

    @Override
    public Integer create(RequirementCoverage requirementCoverage) {
        return (Integer) sessionFactory.getCurrentSession().save(requirementCoverage);
    }
}

然后是实体类:

@Entity
@Table(schema = "wiet", name = "REQ_COVER")
public class RequirementCoverage {

    @SuppressWarnings("unused")
    private static final long serialVersionUID = 1L;

    @Id
    @SequenceGenerator(name = "req_cover_id_gen", sequenceName = "wiet.REQ_COVER_SEQ", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "req_cover_id_gen")
    @Column(name = "RC_ITEM_ID", unique = true, nullable = false)
    private Integer id;

    @OneToOne
    @JoinColumn(name = "RC_REQ_ID", nullable = false)
    private Requirement requirement;

    @Column(name = "RC_ENTITY_ID", nullable = false)
    private Integer entityId;

    @Column(name = "RC_ENTITY_TYPE", nullable = false)
    private String entityType;

    ....setters and gettters...
}

希望我已经说清楚了,感谢阅读。

WietHibernateInterceptor 用于动态改变架构:

@Component
public class WietHibernateInterceptor extends EmptyInterceptor {

    private static final long serialVersionUID = 1L;
    private String schema;

    @Override
    public String onPrepareStatement(String sql) {
        String prepedStatement = super.onPrepareStatement(sql);

        if (prepedStatement.toLowerCase().contains("wiet.".toLowerCase())) {
            /* As @SequenceGenerator ignores schema, sequence squery is manually set to be correct */
            prepedStatement = prepedStatement.replaceAll("`", "\"");
            prepedStatement = prepedStatement.replaceAll("wiet.", this.schema + "\".\"");
        }
        /* Change schema dynamically */
        prepedStatement = prepedStatement.replaceAll("wiet", this.schema);
        return prepedStatement;
    }

    public String getSchema() {
        return schema;
    }

    public void setSchema(String schema) {
        this.schema = schema;
    }

}

【问题讨论】:

  • 您是否在控制台中遇到任何错误?
  • 没有错误。只有没有 INSERT 查询。
  • 你能把WietHibernateInterceptor的代码贴出来吗?它有什么作用。在另一个(不相关的注释)你的@PropertySourc 有缺陷不要使用file:src/main/resources 因为一旦你打包它就是一个罐子或战争,它就会破裂。使用classpath:/Config/database.properties 甚至更好,因为您使用的是Spring Boot,使用默认的application.properties 并让Spring Boot 引导数据源并休眠!。
  • WietHibernateInterceptor 用于在执行 Hibernate 查询之前拦截它。我用它来动态更改架构。我会发布代码。
  • @M.Deinum 感谢您的建议。我是 Spring Boot 的新手,我会弄清楚什么是正确的做法。

标签: java spring oracle hibernate transactions


【解决方案1】:

Hibernate 只会在刷新会话时生成 INSERT 语句。它将在以下情况下刷新会话

  1. @Transactional 方法结束时,
  2. 当它达到其批量限制时(如果配置为批量插入),或者,
  3. 当您调用它检测到的查询需要会话 刷新例如您调用 count() 方法。 Hibernate 会刷新 事务会话并提交它以便 count() 返回一个 记录的准确价值。

我唯一的想法是抛出了一个没有被捕获的异常(但我希望它会显示在您的日志或控制台中),这会导致您的事务回滚,或者由于某种原因@Transactional 会话没有被创建或维护或其他东西。

我要尝试的第一件事是从您的休眠属性中删除休眠方言。 Hibernate 非常出色地根据您提供的驱动程序确定使用哪种方言,并且(尤其是使用 Oracle 驱动程序)如果您强制它使用不同的方言,它可能会产生奇怪的错误。

我要尝试的第二件事是确定 Hibernate 是否抛出了任何 SQLException。我假设您已经使用 Log4J 或您使用的任何日志记录提供程序打开了休眠日志记录。看看你能不能找到一个被抛出的SQLException

【讨论】:

  • 我删除了方言,我在日志中发现的唯一可疑之处是:o.h.hql.internal.ast.ErrorCounter -throwQueryException():没有错误。我想这意味着没有错误。
  • 手动刷新 Hibernate 会话可以产生 INSERT 查询并使程序正常工作,但仍然不知道为什么 @Transactional 不起作用。
  • 我今天从 Spring Boot 1.0.0-RC 升级到了 1.1.16。添加刚刚消失的bug。
【解决方案2】:

您的 @Transaction 注释说 readOnly = true。这意味着在该事务中只允许读取。删除 readOnly = true。 另请查看Spring Transaction management 9.5.6。使用@Transactional。

【讨论】:

  • 感谢您的回答,但我在导入方法上有@Transactional(readonly=false),所以这不是原因。
猜你喜欢
  • 1970-01-01
  • 2014-07-08
  • 2017-02-05
  • 1970-01-01
  • 2023-02-16
  • 2014-02-02
  • 2012-10-16
  • 1970-01-01
  • 2017-07-30
相关资源
最近更新 更多