截至到如今为止,除非你使用 dao.execute(Sql ...)
,一次执行多个 SQL,是事务安全的,其余的状况 均是事务不安全的,好比以下代码:html
Pet pet1 = dao.fetch(Pet.class,"XiaoBai"); Pet pet2 = dao.fetch(Pet.class,"XiaoHei"); pet1.setNickname("BaiBai"); pet2.setNickname("HeiHei"); dao.update(pet1); dao.update(pet2);
尤为是请关注最后两句:java
dao.update(pet1); dao.update(pet2);
当第二句话抛出异常的时候,第一句话不能被回滚。这两条调用就是不事务安全的。若是我想让 pet1 和 pet2 的更新操做是原子性的,它们必须一同成功,一同失败,怎么办呢?sql
Nutz.Dao 提供了简单的解决办法: 事务模板数据库
上一节的例子能够修改成:api
final Pet pet1 = dao.fetch(Pet.class,"XiaoBai"); final Pet pet2 = dao.fetch(Pet.class,"XiaoHei"); pet1.setNickname("BaiBai"); pet2.setNickname("HeiHei"); // Begin transaction Trans.exec(new Atom(){ public void run() { dao.update(pet1); dao.update(pet2); } }); // End transaction
提供一个 org.nutz.trans.Atom 接口的匿名实现,在里面执行的全部的 Dao 操做都是原子性的,由于它们在 同一个 “原子 (Atom)” 里。安全
事务最核心的是原子的界定,在 Nutz.Dao中,界定原子的方法出奇的简单,借助匿名类,你能够随时将一段 代码用你的原子实现包裹住。而 Trans.exec() 方法接受数目可变的原子,每一个原子都是事务性的。闭包
Trans.exec 的函数声明oracle
public static void exec(Atom... atoms);
被原子实现包裹住的代码就是事务安全的,不管它同时操做了多少个 DataSource。 Nutz.Dao 提供的原子接口很是简单,实际上它就是 java.lang.Runnable 的一个别名,下面就是这个接口的 所有代码:函数
package com.zzh.trans; public interface Atom extends Runnable {}
这几乎是我写过的最简单的 Java 类了,正是由于它简单,因此才有无限的威力。你若是查看过 Nutz 的源代 码包,在和数据库操做的地方,你总会和 Atom 不期而遇。不少朋友曾经很不适应匿名类的写法,是的,我在 早期写 Java 的时候也比较讨厌匿名类,可是熟悉了之后,你会真正喜欢上这个东西,就像你写 Javascript 的 一段时候之后,多数人都会喜欢上“闭包”同样。你能够把匿名类看成 Java 给你的闭包。fetch
采用事物模板的来界定事物有一个缺点,这是 Java 语言带来的限制:你有可能须要将一些相关的变量声明成 final 的。 而且在 run 函数中,你只能向外抛 RuntimeException 或其子类。
在 JDBC 的 java.sql.Connection 接口中定义的 setTransactionIsolation 函数能够设置事务的级别 Nutz.Dao 也提供另一个静态函数,容许你设置事务的级别:
Trans.exec 的函数声明
public static void exec(int level, Atom... atoms);
这里的第一个参数 level 和 java.sql.Connection 接口中的 setTransactionIsolation 规定的 level 是同样的。下面 是在 java.sql.Connection 里面关于 level 参数的 JDoc 说明:
它能够是下列常量中的任意一个值:
注意: 不能使用常量 Connection.TRANSACTION_NONE,由于它的意思是“不支持事务”
关于 level 参数的更多说明,请参看 java.sql.Connection 的文档
不一样的数据库,对于 JDBC 事务级别的规范,支持的力度不一样。请参看相应数据库的文档,已 肯定你设置的数据库事务级别 是否被支持。
Nutz 的事务模板能够嵌套吗? 答案是确定的。事实上,Nutz 支持事务模板的无限层级嵌套。
这里,若是每一层嵌套,指定的事务级别有所不一样,不一样的数据库,可能引起不可预知的错误。因此, 嵌套的事务模板的事务,将以最顶层的事务为级别为标准。就是说,若是最顶层的事务级别为 'TRANSACTION_READ_COMMITTED',那么下面所 包含的全部事务,不管你指定什么样的事务级别,都是 'TRANSACTION_READ_COMMITTED', 这一点,由抽象类 Transaction 来保证。 其 setLevel 当被设置了一个大于 0 的整数之后,将再也不 接受任何其余的值。
你能够经过继承 Transaction 来修改这个默认的行为,固然,这个行为修改通常是没有必要的。
另外,你还可能须要知道,经过 Trans.setup 方法,能让整个虚拟机的 Nutz 事务操做都使用你的 Transaction 实现
下面我给出两个例子:
最外层模板决定了整个事务的级别:
Trans.exec(Connection.TRANSACTION_READ_COMMITTED, new Atom(){ public void run(){ dao.update(xxx); dao.update(bbb); // 在下层模板,虽然你指定了新的事务级别,可是这里的事务级别仍是 // 'TRANSACTION_READ_COMMITTED'。在一个事务中,级别一旦设定就不可更改 Trans.exec(Connection.TRANSACTION_SERIALIZABLE, new Atom(){ public void run(){ dao.update(CCC); dao.update(EEE); } }); } });
让整个函数都是事务的:
public void updatePet(final Pet pet){ Trans.exec(new Atom(){ public void run(){ dao.update(pet); dao.update(pet.getMaster()); } }); } // 在另一个函数里,能够这么使用 public void updateDogAndCat(final Pet dog, final Pet cat){ Trans.exec(new Atom(){ public void run(){ updatePet(dog); updatePet(cat); } }); }
org.nutz.trans.Trans 类的 exec()方法,接受数目可变的 Atom 实例,足够方便了吧。 可是它默认只能支 持在一台机器上保证事务性,就是在一个 JVM 里保证代码的事务性。若是跨越多个JVM一块儿组合的 Service, 如何保证事务性呢,很抱歉,Nutz.Dao 的初版的实现里不包括跨越多个JVM依然保证事务性的功能,可是 你若是真的须要这个功能也不要紧,你能够本身写一个 org.nutz.trans.Transaction 的实现,而后在你的应 用启动时,经过
org.nutz.trans.Trans.setup(你的实现类)
替换 Nutz.Dao 的默认实现。