我正在寻找一种在事务中调用多个DAO函数的方法,但我不使用spring或任何此类框架。我们实际拥有的是数据库api类型.jar
,它使用已使用的数据源进行初始化。我想要实现的是让我的业务逻辑级代码执行以下操作:
Connection conn = datasource.getConnection();
conn.setAutoCommit(false);
DAOObject1.query1(params, conn);
DAOObject2.query4(params, conn);
conn.commit();
conn.setAutoCommit(false);
但是我想避免在每个函数中传递连接对象,因为这不是正确的方法。现在,在我们使用的少量事务中,我们正在寻找一种方法来停止将连接对象传递给数据库层,甚至在它之外创建它。我正在寻找以下内容:
//Pseudocode
try{
Datasource.startTransactionLogic();
DAO1.query(params);
DAO2.query(params);
Datasource.endAndCommitTransactionLogic();
}
catch(SQLException e){
Datasource.rollbackTransaction();
}
我可以通过EJB实现这一目标吗?现在我们没有通过注入使用DAO,我们手动创建它们但我们即将迁移到EJB并开始通过容器使用它们。我听说EJB执行的所有查询都是事务性的,但是它如何知道要回滚到什么?通过savepoints
?
修改
让我指出,现在每个DAO
对象的方法都获得了自己的连接对象。以下是我们DAO
类的示例:
public class DAO {
public DTO exampleQueryMethod(Integer id) {
DTO object = null;
String sql = "SELECT * FROM TABLE_1 WHERE ID = ?";
try (
Connection connection = datasourceObject.getConnection();
PreparedStatement statement = connection.prepareStatement(sql)
) {
statement.setInt(1, id);
try (ResultSet resultSet = statement.executeQuery()) {
if (resultSet.next()) {
object = DAO.map(resultSet);
}
}
}
return object;
}
}
现在我们正在为需要处理事务的方法做的事情是获得一个接收Connection
对象的第二个副本:
public void exampleUpdateMethod(DTO object, Connection connection) {
//table update logic
}
我们想要的是避免在我们的'database api'.jar
中使用这样的方法,而是能够在我们的业务逻辑层中定义事务的开始和提交,就像上面的伪代码中提到的那样。 / p>
答案 0 :(得分:1)
我过去所做的是创建一个Repository Object,它接受Database API并生成连接并将连接保存为成员变量。 (以及数据库参考)
然后,我将所有业务层调用作为方法从此存储库对象挂起,以方便调用者。这样..你可以调用,混合,匹配任何调用并使用底层连接,执行回滚,提交等等。
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
public class Foo
{
public Foo(IHttpContextAccessor contextAccessor)
{
_context = contextAccessor.HttpContext;
}
private readonly HttpContext _context;
public bool TokenCorrect()
{
ISessionFeature sessionFeature = _context.Features.Get<ISessionFeature>();
if(sessionFeature != null)
{
int? token = sessionFeature.Session.GetInt32("Token");
if(token.HasValue)
{
// do whatever check you are doing
}
}
return false;
}
}
然后我们接受了这个新对象,并创建了一个已经准备好的池,并在一个新线程中进行管理,而Application只是从池中请求了一个新的“Repository”...然后我们就去了。
答案 1 :(得分:1)
@JBNizet评论是正确的,但是......请三思而后行,是否真的需要迁移到EJB。即使交易也不那么直观:将您的例外包装到javax.ejb.EJBException
中既不灵活也不可读。更不用说其他问题,例如启动时间或集成测试。
从您的问题来看,似乎所有您需要的是依赖注入框架,并支持拦截器。可能的方法:
Spring
绝对是这个领域最受欢迎的CDI
(Weld或OpenWebBeans)自Java EE 6发布以来 - 但完全可以在没有Java EE Application Server的情况下使用(我现在正在使用这种方法 - 它运行良好) Guice
还附带了自己的com.google.inject.persist.Transactional
注释。以上所有三个框架对您的用例同样有用,但还有其他因素需要考虑,例如:
希望它对你有所帮助。
编辑:澄清您的疑虑:
您可以创建自己的Transaction
类,该类将包含从Connection
获取的datasource.getConnection()
。此类事务应该是@RequestScoped
CDI bean,并包含方法方法,如begin()
,commit()
和rollback()
- 它们将调用 connection.commit / rollback 引擎盖下。然后你可以编写一个简单的拦截器,如this one,它将使用提到的事务并在需要的地方启动/提交/回滚它(当然禁用AutoCommit
)。
这是可行的,但要记住,它应该精心设计。这就是为什么几乎每个DI平台/框架都已经提供了用于交易的拦截器。
答案 2 :(得分:1)
@G。 Demecki有正确的想法,但我遵循了不同的实现。 Interceptors
无法解决问题(至少从我看到的问题),因为它们需要附加到应该使用它们的每个函数。一旦拦截了一个拦截器,调用该函数将始终拦截它,这不是我的目标。我希望能够明确定义事务的开始和结束,并使这两个语句之间执行的每个sql都成为SAME事务的一部分,而无需通过参数访问数据库的相关对象(如连接,事务等)通过。我能够实现这一目标的方式(在我看来相当优雅)如下:
我创建了一个ConnectionWrapper
对象,如下所示:
@RequestScoped
public class ConnectionWrapper {
@Resource(lookup = "java:/MyDBName")
private DataSource dataSource;
private Connection connection;
@PostConstruct
public void init() throws SQLException {
this.connection = dataSource.getConnection();
}
@PreDestroy
public void destroy() throws SQLException {
this.connection.close();
}
public void begin() throws SQLException {
this.connection.setAutoCommit(false);
}
public void commit() throws SQLException {
this.connection.commit();
this.connection.setAutoCommit(true);
}
public void rollback() throws SQLException {
this.connection.rollback();
this.connection.setAutoCommit(true);
}
public Connection getConnection() {
return connection;
}
}
我的DAO
个对象本身遵循以下模式:
@RequestScoped
public class DAOObject implements Serializable {
private Logger LOG = Logger.getLogger(getClass().getName());
@Inject
private ConnectionWrapper wrapper;
private Connection connection;
@PostConstruct
public void init() {
connection = wrapper.getConnection();
}
public void query(DTOObject dto) throws SQLException {
String sql = "INSERT INTO DTO_TABLE VALUES (?)";
try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, dto.getName());
statement.executeUpdate();
}
}
}
现在,我可以轻松拥有一个jax-rs
资源@Inject
这些对象并启动并提交一个事务,而不必传递任何Connection
或UserTransaction
。< / p>
@Path("test")
@RequestScoped
public class TestResource {
@Inject
ConnectionWrapper wrapper;
@Inject
DAOObject dao;
@Inject
DAOObject2 dao2;
@GET
@Produces(MediaType.TEXT_PLAIN)
public Response testMethod() throws Exception {
try {
wrapper.begin();
DTOObject dto = new DTOObject();
dto.setName("Name_1");
dao.query(dto);
DTOObject2 dto2 = new DTOObject2();
dto2.setName("Name_2");
dao2.query2(dto2);
wrapper.commit();
} catch (SQLException e) {
wrapper.rollback();
}
return Response.ok("ALL OK").build();
}
}
一切都很完美。没有Interceptors
或环顾InvocationContext
等等。
只有两件事困扰着我:
@Resource(lookup = "java:/MyDBName")
上拥有动态JNDI名称的方法,这让我很烦恼。在我们的AppServer中,我们定义了许多数据源,应用程序使用的数据源是根据与war一起打包的.xml资源文件动态选择的。这意味着我无法在编译时知道数据源JNDI。有通过InitialContext()
环境变量获取数据源的解决方案,但我希望能够从服务器获取它作为资源。我也可以创建一个@Produces
生产者并以这种方式注入它但仍然。ConnectionWrapper
@PostConstruct
之前调用DAOObject
@PostConstruct
DAOObject
。这是正确和理想的行为,但我不明白为什么。我猜测@Inject
ConnectionWrapper
sa @PostConstruct
后,其DAOObjects
优先,因为它必须在/* Unit test task */
gulp.task('pre-test', function() {
return gulp.src(['src/app/**/*.js'])
.pipe(plumber())
.pipe(istanbul({
instrumenter: isparta.Instrumenter,
includeUntested: true
}))
.pipe(istanbul.hookRequire());
});
gulp.task('test:unit', ['pre-test'], function() {
return gulp.src(['src/test/unit/**/*.js'])
.pipe(mocha({
reporter: 'spec'
}))
.pipe(istanbul.writeReports({}));
});
甚至可以开始之前完成但是这个只是一个猜测。