【问题标题】:Hibernate, proper way to modify multiple tables in one transaction休眠,在一个事务中修改多个表的正确方法
【发布时间】:2018-08-22 08:09:13
【问题描述】:

对于我们使用 Hibernate 的项目,我们有一个每个表模式一个 DAO。在每个 DAO 调用中,我们创建一个事务并在它完成执行后提交。

现在,我们需要在同一个事务中更新两个表。这种情况很常见,我们需要发出销售支票,然后从我们的库存中减少所售物品的数量。当然,这一切都必须在同一个事务中完成。

所以,我的问题是,这是在一个事务中修改多个表的正确方法。这是我们的 DAO 之一的示例。如您所见,每个 DAO 仅用于处理一个表。

我们尚未实施计费或股票 DAO。我们想知道哪种方法是满足我们需求的正确方法。任何帮助,都会很棒。

@Service
public class StoreDaoImplementation implements StoreDao {

// We are gonna use a session-per-request pattern, for each data access object (dao).
// In order to use it, we need an session factory that will provide us with sessions for each request.
private SessionFactory factory;

public StoreDaoImplementation() {
    try{
        factory = new Configuration().configure().buildSessionFactory();
    }catch(Exception e){
        e.printStackTrace();
    }
}

/**
 * {@inheritDoc}
 */
@Override
public Tienda findById(String storeId) {
    Session session = factory.getCurrentSession();
    Transaction tx = session.beginTransaction();
    try  {
        return session.get(Tienda.class, storeId);
    } catch (Exception e) {
        return null;
    } finally {
        tx.commit();
    }
}

/**
 * {@inheritDoc}
 */
@Override
public void insert(Tienda tienda) throws Exception {
    Session session = factory.getCurrentSession();
    Transaction tx = session.beginTransaction();
    session.persist(tienda);
    tx.commit();
}

/**
 * {@inheritDoc}
 */
@Override
public void delete(Tienda tienda) throws Exception {
    Session session = factory.getCurrentSession();
    Transaction tx = session.beginTransaction();
    session.delete(tienda);
    tx.commit();
}

/**
 * {@inheritDoc}
 */
@Override
public void update(Tienda tienda) throws Exception {
    Session session = factory.getCurrentSession();
    Transaction tx = session.beginTransaction();
    session.merge(tienda);
    tx.commit();
}

/**
 * {@inheritDoc}
 */
@Override
public void updateSupervisor(List<String> storeIds, String supervisorId) throws Exception {
    Session session = factory.getCurrentSession();
    Transaction tx = session.beginTransaction();
    Query query = session.createQuery("UPDATE Tienda SET supervisor.idEmpleadoPk = :supervisorId WHERE idTiendaPk IN (:storeIds)");
    query.setParameter("supervisorId", supervisorId);
    query.setParameter("storeIds", storeIds);
    query.executeUpdate();
    tx.commit();
}

/**
 * {@inheritDoc}
 */
@Override
public List<Tienda> getAllStores(Integer offset,
                                 Integer rowNumber,
                                 String franchise,
                                 String country,
                                 Boolean state,
                                 String storeName,
                                 Integer orderBy,
                                 Integer orderType) {
    // Always use getCurrentSession, instead of openSession.
    // Only in very odd cases, should the latter be used, then we must remember to close our session.
    // For more information, read the following docs.
    // http://docs.jboss.org/hibernate/core/3.3/reference/en/html/transactions.html#transactions-basics-uow
    Session session = factory.getCurrentSession();
    // All hibernate transactions must be executed in an active transaction.
    Transaction tx = session.beginTransaction();
    try {
        setSessionFilter(session, franchise, country, state, storeName);
        // NOTE: In this query I am using join fetch. This is due a Hibernate bug, that won't allow
        // setting the fetch type in the mapping file. What this does, is that instead of doing multiple selects
        // for each join, it just simply does a big join in the main query.
        // Much faster if you are working with a remote server.
        String hql = "from Tienda T join fetch T.supervisor join fetch T.franquiciaFK join fetch T.pais";
        switch ((orderBy != null) ? orderBy : 4) {
            case 0:
                hql += " order by T.franquiciaFK.franquiciaPk";
                break;
            case 1:
                hql += " order by T.pais.paisPk";
                break;
            case 2:
                hql += " order by T.provincia";
                break;
            case 3:
                hql += " order by T.supervisor.idEmpleadoPk";
                break;
            default:
                hql += " order by T.franquiciaFK.orden";
                break;
        }
        switch ((orderType != null) ? orderType : 0) {
            case 0:
                hql += " asc";
                break;
            case 1:
                hql += " desc";
                break;
        }
        Query query = session.createQuery(hql);
        query.setFirstResult(offset);
        query.setMaxResults(rowNumber-1);
        return query.list();
    } catch (Exception e) {
        return null;
    } finally {
        tx.commit();
    }
}

/**
 * {@inheritDoc}
 */
@Override
public List<Tienda> getStoresBySupervisor(String supervisorId) {
    Session session = factory.getCurrentSession();
    Transaction tx = session.beginTransaction();
    try {
        // NOTE: In this query I am using join fetch. This is due a Hibernate bug, that won't allow
        // setting the fetch type in the mapping file. What this does, is that instead of doing multiple selects
        // for each join, it just simply does a big join in the main query.
        // Much faster if you are working with a remote server.
        Query query = session.createQuery("from Tienda T join fetch T.supervisor join fetch T.franquiciaFK join fetch T.pais where T.supervisor.idEmpleadoPk = :supervisorId");
        query.setParameter("supervisorId", supervisorId);
        return query.list();
    } catch (Exception e) {
        return null;
    } finally {
        tx.commit();
    }
}

/**
 * {@inheritDoc}
 */
@Override
public Integer countAllStores(String franchise, String country, Boolean state, String storeName) {
    Session session = factory.getCurrentSession();
    Transaction tx = session.beginTransaction();
    try {
        // Check that the filters are not null.
        setSessionFilter(session, franchise, country, state, storeName);
        Query query = session.createQuery("select count(*) from Tienda");
        return ((Long) query.iterate().next()).intValue();
    } catch (Exception e) {
        return null;
    } finally {
        tx.commit();
    }
}

/**
 * Given that we already know the all filters we can use in our stores' queries,
 * we can make a method to configure them.
 * @param session Actual session that will query the DB.
 * @param franchise Franchise filter. Only returns those store of the specified franchise.
 * @param country Country filter. Only returns those store of the specified country.
 * @param state State filter. Only returns those stores of the specified state.
 */
private void setSessionFilter(Session session, String franchise, String country, Boolean state, String name) {
    if(franchise != null && !franchise.isEmpty()) {
        session.enableFilter("storeFranchiseFilter").setParameter("franchiseFilter", franchise);
    }
    if(country != null && !country.isEmpty()) {
        session.enableFilter("storeCountryFilter").setParameter("countryFilter", country);
    }
    if(state != null) {
        session.enableFilter("storeStateFilter").setParameter("stateFilter", state);
    }
    if(name != null && !name.isEmpty()) {
        session.enableFilter("storeNameFilter").setParameter("nameFilter", "%"+name+"%");
    }
}
}

【问题讨论】:

    标签: java sql-server hibernate spring-mvc


    【解决方案1】:

    您可以使用Transactional 注释,它提供了更多控制,例如异常回滚和处理多个事务。

    public interface BillingService {
    
        public BillingDAO getBalance();
    
    }
    
    @Service(value = "billingService")
    @Transactional("transactionManager")
    public class BillingServiceImpl implements BillingService {
    
        @Autowired
        private SessionFactory sessionFactory;
    
        @Override
        // Transactional // you can have method level transaction manager, which can be different from one method to another
        public BillingDAO getBalance(long id) {
            return sessionFactory.getCurrentSession().get(BillingDAO.class, id);
        }
    
    }
    
    public interface StockService {
    
        public StockDAO getStock();
    
    }
    
    @Service(value = "stockService")
    @Transactional("transactionManager")
    public class StockServiceImpl implements StockService {
    
        @Autowired
        private SessionFactory sessionFactory;
    
        @Autowired
        private BillingService billingService;
    
        @Override
        // Transactional
        public StockDAO getStock(long id) {
    
            // if you want to use billing related changes, use billing server which is autowired
            BillingDAO billingDAO = billingService.getBalance(id);
    
            return sessionFactory.getCurrentSession().get(StockDAO.class, billingDAO.getStockId());
        }
    
    }
    
    @Configuration
    @EnableTransactionManagement
    public class DatabaseConfig {
    
        @Autowired
        private ApplicationContext appContext;
    
        @Autowired
        private ApplicationProperties applicationProperties;
    
        @Bean
        public HikariDataSource getDataSource() {
            HikariDataSource dataSource = new HikariDataSource();
    
            dataSource
                .setDataSourceClassName(applicationProperties.getHibernateDatasource());
            dataSource.addDataSourceProperty("databaseName", applicationProperties.getRdbmsDatabase());
            dataSource.addDataSourceProperty("portNumber", applicationProperties.getRdbmsPort());
            dataSource.addDataSourceProperty("serverName", applicationProperties.getRdbmsServer());
            dataSource.addDataSourceProperty("user", applicationProperties.getRdbmsUser());
            dataSource.addDataSourceProperty("password", applicationProperties.getRdbmsPassword());
    
            return dataSource;
        }
    
        @Bean("transactionManager")
        public HibernateTransactionManager transactionManager() {
            HibernateTransactionManager manager = new HibernateTransactionManager();
            manager.setSessionFactory(hibernate5SessionFactoryBean().getObject());
            return manager;
        }
    
        @Bean(name = "sessionFactory")
        public LocalSessionFactoryBean hibernate5SessionFactoryBean() {
            LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
            localSessionFactoryBean.setDataSource(appContext
                    .getBean(HikariDataSource.class));
            localSessionFactoryBean.setAnnotatedClasses(BillingDAO.class);
    
            Properties properties = new Properties();
    
            // properties.put("hibernate.current_session_context_class","thread");
            // // because I am using Spring, it will take care of session context
            /*
             * 
             * Spring will by default set its own CurrentSessionContext
             * implementation (the SpringSessionContext), however if you set it
             * yourself this will not be the case. Basically breaking proper
             * transaction integration.
             * 
             * Ref:
             * https://stackoverflow.com/questions/18832889/spring-transactions-and-hibernate-current-session-context-class
             */
            properties.put("hibernate.dialect",
                    applicationProperties.getHibernateDialect());
    
            properties.put("hibernate.hbm2ddl.auto", applicationProperties.getHibernateHbm2ddlAuto());
            properties.put("hibernate.show_sql", applicationProperties.getShowSql());
            // properties.put("hibernate.hbm2ddl.import_files",
            // "/resource/default_data.sql"); // this will execute only
            // when hbm2ddl.auto is set to "create" or "create-drop"
            // properties.put("connection.autocommit", "true");
    
            localSessionFactoryBean.setHibernateProperties(properties);
            return localSessionFactoryBean;
        }
    }
    

    【讨论】:

    • 感谢您的信息。然而,我正在更多地研究什么是进行修改多个表的事务的正确方法。比如,我是否需要一个 DAO 来处理单个事务,如果需要,我是否需要一个服务来调用该 DAO。我想要找到的是正确的方法。
    • @AlainCruz 当你说修改多个表时,事务应该是独立的,或者如果一个更新失败,那么一切都应该回滚?您可以在单个事务中拥有多个 DAO,这意味着您可以在单个事务中更新多个表,但我建议创建单独的服务来处理单独的 DAO。
    • 如果一次更新或插入失败,我应该回滚事务。到目前为止,我每个 DAO 都有一项服务。那么,您推荐的是制作一个可以与多个 DAO 一起使用的单独服务?例如,我将有一个 DAO 用于计费表,另一个用于库存表,还有一个用于处理开票和减少库存的行为?最后一个服务应该有两个 DAO,对吧?
    • 不,最后一个服务器应该使用另一个自动连接的服务。我用一个例子更新了我的答案,您可以从DatabaseConfig 中举例说明如何创建transactionManager 对象和sessionFactory 对象,这将是您如何使用库存和计费服务的后勤部分。我希望它有所帮助。
    • 好的,我现在明白了。我将查看您的代码并了解如何使用我们的配置来实现它。非常感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 2011-10-18
    • 2011-01-25
    • 2017-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-05
    相关资源
    最近更新 更多