EJB3之JPA程序结构,完美的异常处理

沙老师 2009-08-29 08:17:29
首先得用到封装了基本实体操作的EntityManagerHelper类,这在网上到处都是:

public class EntityManagerHelper {

private static final EntityManagerFactory emf;
private static final ThreadLocal<EntityManager> threadLocal;
private static final Logger logger;


static {
emf = Persistence.createEntityManagerFactory("EjbTestPU");
threadLocal = new ThreadLocal<EntityManager>();
logger = Logger.getLogger("EjbTestPU");
logger.setLevel(Level.ALL);
}

public static EntityManager getEntityManager() {
EntityManager em = threadLocal.get();
if (em == null || !em.isOpen()) {
em = emf.createEntityManager();
threadLocal.set(em);
}
return em;
}

public static void closeEntityManager() {
EntityManager em = threadLocal.get();
threadLocal.set(null);
if (em != null && em.isOpen()) {
em.close();
}
}

public static void beginTransaction() {
getEntityManager().getTransaction().begin();
}

public static void commit() {
getEntityManager().getTransaction().commit();
}

public static void rollback() {
getEntityManager().getTransaction().rollback();
}

public static Query createQuery(String query) {
return getEntityManager().createQuery(query);
}
}

然后呢,所有需要或者不需要事务操作的地方都可以这样写:


try {
EntityManagerHelper.beginTransaction();

// 这里是自己的数据库操作

EntityManagerHelper.commit();
System.out.println("提交成功!");
} catch (Exception e) {
System.out.println("提交异常:" + e);
try {
EntityManagerHelper.rollback();
System.out.println("回滚成功");
} catch (Exception ex) {
System.out.println("回滚异常:" + ex);
}
} finally {
EntityManagerHelper.closeEntityManager();
}

其中重点要注意的地方就是 rollback也需要捕获异常。只有捕获了所有异常,程序才不会“飞”掉。

很显然,如果程序中所有实体操作的地方都这样写,不累死才怪呢,所以可以写这样一个抽象类封装异常处理:

public abstract class Transaction {

final static Logger logger = Logger.getLogger(Transaction.class);

public abstract void run();

public void start() {
EntityManagerHelper EntityManagerHelper = EntityManagerHelper.getInstance();
try {
EntityManagerHelper.beginTransaction();
run();
EntityManagerHelper.commit();
logger.info("事务提交成功");
} catch (Exception e) {
System.out.println("事务提交异常:" + e);
try {
EntityManagerHelper.rollback();
System.out.println("事务回滚成功");
} catch (Exception ex) {
System.out.println("事务回滚异常:" + ex);
}
} finally {
EntityManagerHelper.closeEntityManager();
}
}
}

使用的时候只需要继承此抽象类即可,例如:


(new Transaction() {

@Override
public void run() {

// 这里是自己的数据库操作

}
}).start();
...全文
335 14 打赏 收藏 转发到动态 举报
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
沙老师 2009-09-01
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 bao110908 的回复:]
呵呵,能明白你的意思,如果我想执行的不是 persist,而是 find 呢?查出来的对象如何返回回去?

还有,为什么要使用 Runnable 接口?
[/Quote]

Linux终于能打汉字了...用Runnable其实就是因为它很简单,就一个run方法,呵呵,这样就不需要自己写接口了,而且显得很酷。查询操作返回值应该可以这样写吧:

final List<XX> xx;

EntityManagerHelper.doTransaction(new Runnable() {

public void run() {
// 获取xx
...
}
});

return xx;

没有试过,我程序中基本上所有业务逻辑都包在doTransaction中,图个方便。不过估计后面肯定会碰到问题的
  • 打赏
  • 举报
回复
呵呵,能明白你的意思,如果我想执行的不是 persist,而是 find 呢?查出来的对象如何返回回去?

还有,为什么要使用 Runnable 接口?
feishare 2009-08-30
  • 打赏
  • 举报
回复
学习了,帮顶了
  • 打赏
  • 举报
回复
小哥,你起得好早啊,呵呵
  • 打赏
  • 举报
回复
不是很明白,为什么 public void doTransaction(Runnable runnable) 这里要使用 Runnable 接口?

如果数据库操作需要返回些什么东西,怎么处理?

如果事务的边界不在 DAO 上,而是在业务层上,也就是说一个业务逻辑需要调用多个数据库操作方法以完成一个事务时怎么处理?

楼主有兴趣的话,可以去找找事务上下文模式方面的资料,事务上下文是 J2EE 设计模式之一,核心思想是先如何确定事务边界,怎么样不在代码中出现事务处理语句等等。事务上下文一般是采用动态代理将事务逻辑切入到代码中去的。

Spring,以及 EJB 容器都是采用事务上下文模式进行设计的。

我到网上找了一下事务上下文的资料并不是很多,只看到一篇:

http://www.javaresearch.org/article/59935.htm

粗略地看了一下,这篇文章基本上是事务上下文模式的体现了,有兴趣的话可以去看一下。
沙老师 2009-08-30
  • 打赏
  • 举报
回复
可能文章中没有表达得很清楚,例如需要这样一个操作:

Student s = new Student("张三"); // 创建一个学生实体
em.persist(s); // 存到数据库中

如果用最笨拙的方法,那么就是这样写的:

EntityManagerFactory emf = Persistence.createEntityManagerFactory("EjbTestPU");
EntityManager em = emf.createEntityManager();
EntityTransaction et = em.getTransaction();
try {
et.begin();
Student s = new Student("张三");
em.persist(s);
et.commit();
logger.info("事务执行成功");
} catch (PersistenceException e) {
logger.severe("持久化异常:" + e);
try {
et.rollback();
logger.info("事务回滚成功");
} catch (Exception ex) {
logger.severe("事务回滚异常:" + ex);
}
} catch (IllegalStateException e) {
logger.severe("非法状态异常:" + e);
try {
et.rollback();
logger.info("事务回滚成功");
} catch (Exception ex) {
logger.severe("事务回滚异常:" + ex);
}
} catch (IllegalArgumentException e) {
logger.severe("非法参数异常:" + e);
try {
et.rollback();
logger.info("事务回滚成功");
} catch (Exception ex) {
logger.severe("事务回滚异常:" + ex);
}
} finally {
em.close();
}

这种写法有两个缺点:一是太累赘,其中异常处理占了绝大部分,如果项目中所有地方都这样写那肯定要累死,以后如果需要修改也得改死;二是如果涉及到多线程操作会很不方便。

而EntityManagerHelper类做了简单的包装,用起来方便多了,而且也线程安全了,但是还是很累赘:

EntityManager em = EntityManagerHelper.getEntityManager();
try {
EntityManagerHelper.beginTransaction();
Student s = new Student("张三");
em.persist(s);
EntityManagerHelper.commit();
logger.info("事务执行成功");
} catch (PersistenceException e) {
logger.severe("持久化异常:" + e);
try {
EntityManagerHelper.rollback();
logger.info("事务回滚成功");
} catch (Exception ex) {
logger.severe("事务回滚异常:" + ex);
}
} catch (IllegalStateException e) {
logger.severe("非法状态异常:" + e);
try {
EntityManagerHelper.rollback();
logger.info("事务回滚成功");
} catch (Exception ex) {
logger.severe("事务回滚异常:" + ex);
}
} catch (IllegalArgumentException e) {
logger.severe("非法参数异常:" + e);
try {
EntityManagerHelper.rollback();
logger.info("事务回滚成功");
} catch (Exception ex) {
logger.severe("事务回滚异常:" + ex);
}
} finally {
EntityManagerHelper.closeEntityManager();
}

如果在EntityManagerHelper中加上doTransaction方法,那么只需要这样写:

EntityManagerHelper.doTransaction(new Runnable() {

public void run() {
EntityManager em = EntityManagerHelper.getEntityManager();
Student s = new Student("张三");
em.persist(s);
}
});

简单多了。选用Runnable接口的原因唯一是只有一个可重载的run方法,简单容易记忆。其实JDK很多地方也用到了此技巧,例如EvevtQueue.invokeLater方法。

完整的EntityManagerHelper类是这样的:


import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

public class EntityManagerHelper {

private static final EntityManagerFactory emf;
private static final ThreadLocal<EntityManager> threadLocal;
private static final Logger logger;


static {
emf = Persistence.createEntityManagerFactory("EjbTestPU");
threadLocal = new ThreadLocal<EntityManager>();
logger = Logger.getLogger("EjbTestPU");
logger.setLevel(Level.ALL);
}

public static EntityManager getEntityManager() {
EntityManager em = threadLocal.get();
if (em == null || !em.isOpen()) {
em = emf.createEntityManager();
threadLocal.set(em);
}
return em;
}

public static void closeEntityManager() {
EntityManager em = threadLocal.get();
threadLocal.set(null);
if (em != null && em.isOpen()) {
em.close();
}
}

public static void beginTransaction() {
getEntityManager().getTransaction().begin();
}

public static void commit() {
getEntityManager().getTransaction().commit();
}

public static void rollback() {
getEntityManager().getTransaction().rollback();
}

public static Query createQuery(String query) {
return getEntityManager().createQuery(query);
}

public static void doTransaction(Runnable runnable) {
try {
EntityManagerHelper.beginTransaction();
runnable.run();
EntityManagerHelper.commit();
logger.info("事务执行成功");
} catch (PersistenceException e) {
logger.severe("持久化异常:" + e);
try {
EntityManagerHelper.rollback();
logger.info("事务回滚成功");
} catch (Exception ex) {
logger.severe("事务回滚异常:" + ex);
}
} catch (IllegalStateException e) {
logger.severe("非法状态异常:" + e);
try {
EntityManagerHelper.rollback();
logger.info("事务回滚成功");
} catch (Exception ex) {
logger.severe("事务回滚异常:" + ex);
}
} catch (IllegalArgumentException e) {
logger.severe("非法参数异常:" + e);
try {
EntityManagerHelper.rollback();
logger.info("事务回滚成功");
} catch (Exception ex) {
logger.severe("事务回滚异常:" + ex);
}
} finally {
EntityManagerHelper.closeEntityManager();
}
}
}
沙老师 2009-08-30
  • 打赏
  • 举报
回复
呵呵文章看了,确实我的设计比起来要差得远,不过感觉基本思路是一样的:如果在项目中大量存在这种情况:

// 固定的代码
...
// 可变的代码
...
// 固定的代码
...

那么就必须设计一种方式来“包装”固定的代码,把可变的代码类似于“参数”一样传递到“包装”中执行。想如果Java支持闭包的话,会更简单些。
沙老师 2009-08-30
  • 打赏
  • 举报
回复
是在J2SE环境下使用的...不是在J2EE容器中
沙老师 2009-08-30
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 bao110908 的回复:]
不是很明白,为什么 public void doTransaction(Runnable runnable) 这里要使用 Runnable 接口?

如果数据库操作需要返回些什么东西,怎么处理?

如果事务的边界不在 DAO 上,而是在业务层上,也就是说一个业务逻辑需要调用多个数据库操作方法以完成一个事务时怎么处理?

楼主有兴趣的话,可以去找找事务上下文模式方面的资料,事务上下文是 J2EE 设计模式之一,核心思想是先如何确定事务边界,怎么样不在代码中出现事务处理语句等等。事务上下文一般是采用动态代理将事务逻辑切入到代码中去的。

Spring,以及 EJB 容器都是采用事务上下文模式进行设计的。

我到网上找了一下事务上下文的资料并不是很多,只看到一篇:

http://www.javaresearch.org/article/59935.htm

粗略地看了一下,这篇文章基本上是事务上下文模式的体现了,有兴趣的话可以去看一下。
[/Quote]he he kan kan (sorry linux da bu liao zhong wen...)
andycpp 2009-08-29
  • 打赏
  • 举报
回复
注入是个好东西
  • 打赏
  • 举报
回复
在 EJB3 中根本就不是这样获得 EntityManager 对象的,而是通过 @PersistenceContext 注入的。

而且在 EJB3 中 JPA 的事务也不是这样处理的,是通过 @TransactionAttribute 声明由容器管理的。
woming66 2009-08-29
  • 打赏
  • 举报
回复
很好,帮你顶!
沙老师 2009-08-29
  • 打赏
  • 举报
回复
呵呵经验总结,欢迎各位指教
沙老师 2009-08-29
  • 打赏
  • 举报
回复
还有一种方法:
在EntityManagerHepler中新建一个方法doTransaction,这样貌似更符合Java的一般习惯:
public void doTransaction(Runnable runnable) {
try {
EntityManagerHelper.beginTransaction();
runnable.run();
EntityManagerHelper.commit();
logger.info("事务提交成功");
} catch (Exception e) {
logger.error("事务提交异常:" + e);
try {
EntityManagerHelper.rollback();
logger.info("事务回滚成功");
} catch (Exception ex) {
logger.error("事务回滚异常:" + ex);
}
} finally {
EntityManagerHelper.closeEntityManager();
}
}

进一步,Exception可以细化成PersistenceException,这样避免和其它异常相混淆,再加上后来查JavaDoc发现漏掉两个异常,所以这样应该完美了:
public void doTransaction(Runnable runnable) {
EntityManagerHelper EntityManagerHelper = getInstance();
try {
EntityManagerHelper.beginTransaction();
runnable.run();
EntityManagerHelper.commit();
logger.info("事务执行成功");
} catch (PersistenceException e) {
logger.error("持久化异常:" + e);
try {
EntityManagerHelper.rollback();
logger.info("事务回滚成功");
} catch (Exception ex) {
logger.error("事务回滚异常:" + ex);
}
} catch (IllegalStateException e) {
logger.error("非法状态异常:" + e);
try {
EntityManagerHelper.rollback();
logger.info("事务回滚成功");
} catch (Exception ex) {
logger.error("事务回滚异常:" + ex);
}
} catch (IllegalArgumentException e) {
logger.error("非法参数异常:" + e);
try {
EntityManagerHelper.rollback();
logger.info("事务回滚成功");
} catch (Exception ex) {
logger.error("事务回滚异常:" + ex);
}
} finally {
EntityManagerHelper.closeEntityManager();
}
}

使用的时候有两点需要注意:

1、Runnable中应该避免抛出PersistenceException中包含的两个子异常NonUniqueResultException, NoResultException,解决方法是禁用Query.getSingleResult()方法,取而代之以getResultList()方法 - 这应该是一个好习惯。

2、Runnable如果有异常捕获的结构,注意不要捕获通用的Exception异常,而应该具体异常具体对待,避免“吃掉”JPA的异常 - 这也应该是一个好习惯。

67,512

社区成员

发帖
与我相关
我的任务
社区描述
J2EE只是Java企业应用。我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。
社区管理员
  • Java EE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧