如何使用 JDBC 创建事务性 DAO

Posted

技术标签:

【中文标题】如何使用 JDBC 创建事务性 DAO【英文标题】:How to create transactional DAOs using JDBC 【发布时间】:2019-11-16 11:20:10 【问题描述】:

我现在正在使用 JDBC,并希望为数据访问和业务逻辑创建单独的层。我为每个实体创建了几个 DAO,并为我的业务逻辑创建了几个服务。但是我遇到了交易问题。在每个 DAO 中,我都有 CRUD,我在每个操作中打开连接并在关闭它之后。但是如果我需要在事务中使用几个操作,它就行不通了。

所以我为整个 DAO 创建了一个连接,但我需要为 DAO 外部的每个操作打开和关闭连接。

我的 DAO 示例

public class UserDAOImpl implements UserDAO 

    private Connection connection;

    public UserDAO(Connection connection) 
        this.connection = connection;
    

    // CRUD operations

抽象 DAO 工厂

public abstract class DAOFactory 

    public abstract UserDAO getUserDAO();
    public abstract ItemDAO getItemDAO();
    public abstract OrderDAO getOrderDAO();
    public abstract RoleDAO getRoleDAO();

    public static DAOFactory getDAOFactory(Class<? extends DAOFactory> factoryClass) throws IllegalAccessException, InstantiationException 
        return factoryClass.newInstance();
    

MySQL DAO工厂实现示例

public class mysqlDAOFactory extends DAOFactory 

    private UserDAO userDAO;
    private ItemDAO itemDAO;
    private OrderDAO orderDAO;
    private RoleDAO roleDAO;

    @Override
    public UserDAO getUserDAO() 
        if (userDAO == null) 
            userDAO = new UserDAOImpl(getConnection());
        
        return userDAO;
    

    @Override
    public ItemDAO getItemDAO() 
        if (itemDAO == null) 
            itemDAO = new ItemDAOImpl(getConnection());
        
        return itemDAO;
    

    @Override
    public OrderDAO getOrderDAO() 
        if (orderDAO == null) 
            orderDAO = new OrderDAOImpl(getConnection());
        
        return orderDAO;
    

    @Override
    public RoleDAO getRoleDAO() 
        if (roleDAO == null) 
            roleDAO = new RoleDAOImpl(getConnection());
        
        return roleDAO;
    

    static 
        try 
            Class.forName("com.mysql.cj.jdbc.Driver");
         catch (ClassNotFoundException e) 
            e.printStackTrace();
        
    

    public static Connection getConnection() 
        Connection connection = null;
        Context initCtx = null;
        try 
            initCtx = new InitialContext();
            Context envCtx = (Context) initCtx.lookup("java:comp/env");
            DataSource ds = (DataSource) envCtx.lookup("jdbc/mysql");
            connection = ds.getConnection();
         catch (NamingException | SQLException e) 
            e.printStackTrace();
        
        return connection;
    

DAO 方法示例

public Optional<User> findById(Long id) 
        User user = null;
        try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM shop.user WHERE id = ?", Statement.RETURN_GENERATED_KEYS)) 
            statement.setLong(1, id);
            ResultSet resultSet = statement.executeQuery();
            resultSet.next();
            user = userMapper.map(resultSet);
         catch (SQLException e) 
            e.printStackTrace();
        
        return Optional.ofNullable(user);
    

【问题讨论】:

嗯...我想不出简单的方法,但在我脑海中,您可以使用由 AspectJ 提供支持的拦截器。拦截器将负责管理和提供 DAO 使用的连接实例。 @FanaSithole 我不使用 Spring。 是的,我知道,AspectJ 不是 Spring,Spring AOP 相当于 AspectJ @FanaSithole 如果我不使用任何框架会更好。 如果您不想要框架,我建议您对数据访问层使用代理模式来执行您在 Aspect 中所做的相同操作 【参考方案1】:

通常,您不必在 DAO 级别处理事务。最好在服务层应用事务管理,因为这样可以控制何时需要/不需要事务的逻辑,还可以控制回滚策略等其他方面。

在您的情况下,您可以查看 JTA 文档。此 API 提供注释和其他工具来轻松管理您的事务:)

【讨论】:

所以我的问题是如何在 DAO 之间传递连接。 @JohnDev,让你的DAOFactory 对连接创建一无所知;将该部分移动到服务层 (getConnection()) 并将Connection 对象从服务注入DAOFactory 中,而不是从工厂中。 @MickMnemonic 如果我将在我的DAOFactory 中管理连接是否正常?例如,创建用于开始结束交易的方法。 @JohnDev 也许可以通过使用 Java 标准 @Inject@Named 注释来尝试使用依赖注入,这些是独立于框架的【参考方案2】:

现在我已经创建了用于管理所有 DAO 之间的连接的类。我已将抽象类 DAOFactory 更改为接口并创建抽象类 JdbcDaoFactory 并扩展了我的 DaoManager 类。

DAOFactory

public interface DAOFactory 

    UserDAO getUserDAO();
    ItemDAO getItemDAO();
    OrderDAO getOrderDAO();
    RoleDAO getRoleDAO();

public abstract class JdbcDaoFactory implements DAOFactory 

    protected Connection connection;

    private UserDAO userDAO;
    private ItemDAO itemDAO;
    private OrderDAO orderDAO;
    private RoleDAO roleDAO;

    protected JdbcDaoFactory(Connection connection) 
        this.connection = connection;
    

    @Override
    public UserDAO getUserDAO() 
        if (userDAO == null) 
            userDAO = new UserDAOImpl(connection);
        
        return userDAO;
    

    @Override
    public ItemDAO getItemDAO() 
        if (itemDAO == null) 
            itemDAO = new ItemDAOImpl(connection);
        
        return itemDAO;
    

    @Override
    public OrderDAO getOrderDAO() 
        if (orderDAO == null) 
            orderDAO = new OrderDAOImpl(connection);
        
        return orderDAO;
    

    @Override
    public RoleDAO getRoleDAO() 
        if (roleDAO == null) 
            roleDAO = new RoleDAOImpl(connection);
        
        return roleDAO;
    

public class JdbcDaoManager extends JdbcDaoFactory implements AutoCloseable 

    public JdbcDaoManager() 
        super(DbHelper.getConnection());
    

    public void beginTransaction() throws SQLException 
        connection.setAutoCommit(false);
    

    public void commitTransaction() throws SQLException 
        connection.commit();
        connection.setAutoCommit(true);
    

    public void rollbackTransaction() throws SQLException 
        connection.rollback();
        connection.setAutoCommit(true);
    

    @Override
    public void close() throws SQLException 
        connection.close();
    

但我仍然不确定这个解决方案有多正确...... 这是它的用法示例。

有交易

try (JdbcDaoManager daoManager = new JdbcDaoManager()) 
    try 
        daoManager.beginTransaction();
        daoManager.getUserDAO().insert(new User("user1", "12345"));
        daoManager.getUserDAO().insert(new User("user2", "12345"));
        daoManager.getUserDAO().insert(new User("user3", "12345"));
        daoManager.commitTransaction();
     catch (SQLException e) 
        daoManager.rollbackTransaction();
    
 catch (SQLException e) 
    e.printStackTrace();

无交易

try (JdbcDaoManager daoManager = new JdbcDaoManager()) 
   daoManager.getUserDAO().insert(new User("user1", "12345"));
 catch (SQLException e) 
   e.printStackTrace();

【讨论】:

以上是关于如何使用 JDBC 创建事务性 DAO的主要内容,如果未能解决你的问题,请参考以下文章

Spring JDBC:如何创建表?

IOC/JDBC/DAO - 此代码示例如何工作?

如何使用 JDBC 处理 DAO 之间的交互? [复制]

Spring事务管理

java - 如何在Java Spring和Hibernate的单个事务中管理2个DAO方法?

如何使用JDBC实现数据访问对象层(DAO)