はじめに:
私たちは春の宣言的トランザクションを使用するたびに、私たちはすることができ、クラスやメソッドに@Transactionalを宣言する必要がありますが、我々はトランザクション障害時にそれが発生していませんか?
大きなトランザクションの7種類は春ために失敗します
- MyISAMテーブルがトランザクションをサポートしていないため、このようなMyISAMテーブルをMySQLを使用して、エンジンがあるとして、トランザクションはない仕事、それはInnoDBのに変更することができます。
- 春+ MVCの場合は、コンテキスト:コンポーネント・スキャンの繰り返しの問題をスキャンするには、トランザクションが失敗する可能性があります。
- @Transactionalコメント開いた構成、リスナーはDispatcherServletの設定を置いた場合、トランザクションはまた、動作しない、負荷に配置する必要があります。
- @Transactional注釈方法は、唯一の公共の視認性にも適用することができます。あなたがプライベートまたは保護された方法@Transactionalアノテーションを使用している場合、それはエラーではありません、トランザクションは失敗します。
- スプリングチームは@Transactionalではなく、達成されるべき任意のインターフェイスタイプに特定のクラスのノートに(メソッドまたはクラス)をお勧めします。あなたは、エージェントインターフェースベースの場合に有効になるように、それを設定するだけのインターフェイス上の@Transactionalアノテーションを使用。注釈は、クラスベースのプロキシが使用されている場合、そのトランザクションは、プロキシクラスに基づいて認識されない設定することをその手段は継承されず、オブジェクトがされないために包装(ただし、インターフェイスに作用します上記は低下し、私が)出て見つけることができませんでした実現可能な業務です。
- 取引方法は取引方法を失敗と呼ばれ、トランザクション内で同じクラスの別のメソッドを呼び出します。(ネイティブオブジェクトを使用して、そのような薬剤は、失敗しました)
- スロー新しい例外(「XXXXXXXXXXXX」)のキャプチャを使用するには、例外コード
容器スプリングサンズ(のDispatcherServlet障害に構成トランザクション)
親容器:ルートWebAplicationContextの
子コンテナ:サーブレットWebAplicationContextの
図は、あなたが必要な子コンテナが親コンテナになることですかを見ることができますが、コンテナは、子コンテナの親ではないです。私たちは、Webアプリケーション開発のための春+ SpringMVCを使用しているときに、春は(このものの、それを自分自身のことを行う、スキャンしませDAOとサービス層を行うライン上のコントローラをスキャンするSpringMVCの責任、DAOとサービス層をスキャンするための責任があります)間違って行くが、メモリ空間の無駄はありません。
この図はまた、我々はトランザクションでSpringMVCを設定する理由スキャンは------「取引が一般的SpringMVCの業務におけるサービス層、我々スキャン設定で行われ有効になりません、私たちは親コンテナにはできません説明しますトランザクションの失敗につながるので、結果として。。。。。
これは、代わりにwebMVCでの構成で、中にリスナーにロードする必要があります
取引方法は、トランザクション内で同じクラスの別のメソッドを呼び出すために、取引方法が失敗と呼ばれています
(根本原因 - 「JDKエージェントまたはエージェントCGLIBの失敗は、ネイティブオブジェクトを使用します)
@Service
public class CityServiceImpl implements CityService {
private final static Logger LOGGER = LoggerFactory.getLogger(CityServiceImpl.class);
@Autowired
private CityDao cityDao;
private CityServiceImpl proxy;
@Override
@Transactional
public void parent() {
LOGGER.info("======================insertParent()===================");
//这种情况child事务失效
//根据动态代理分析 此处的child()不是由 AopProxy调用的 而是 this对象
try {
child();
}catch (Exception e){
LOGGER.error("parent catch child execption ",e);
throw new RuntimeException();
}
//以下代码为Parent业务
City city= new City();
city.setProvinceId(Long.valueOf(99));
city.setCityName("parent");
city.setDescription("parentparentparentparent");
cityDao.insertCity(city);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void child() {
LOGGER.info("======================insertChild()===================");
City city= new City();
city.setProvinceId(Long.valueOf(99));
city.setCityName("Child");
city.setDescription("ChildChildChildChildChildChild");
cityDao.insertCity(city);
int a=1/0;//此处异常
}
私たちは、このコードは時に子供が異常、データがロールバックされていない挿入していることがわかります実行した後は、トランザクションの失敗が起こりました。私が持っているものを分析するために私たちの失敗によって与えられた次の春のトランザクションの理由は、我々は最初の5つのエラーをしないので、この時間は私たちの質問があると仮定すると、最終的にはトランザクション障害の子のアプローチにそれを原因は何ですか?
私たちは、SpringAopが達成知っているとの取引はJDKのダイナミックプロキシまたはCGLIBによって行われ、その後、彼らはそれの失敗ではないでしょうか?
失敗の本当の原因
理由春トランザクションエラー-----「シンプルな平面分析
春のダイナミックプロキシは現在、2つの方法がありますが、一つはCGLIBあり、一つはJDKで、2つの実装は同じではありませんが、トランザクションは、同じ理由で失敗しました。次は、例として、JDKのダイナミックプロキシを我々
JDKProxy
public interface JDKProxy {
void parent();
void child();
}
JDKProxyImpl
public class JDKProxyImpl implements JDKProxy{
public void parent() {
System.out.println("parent......");
// child();
}
@Override
public void child() {
System.out.println("child......");
}
}
JDKProxyTest
package fileTest;/*
@author yyc
@DESCRIPTION
@create 2019/8/19
*/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxyTest {
public static void main(String[] args) {
JDKProxy jdkProxy = new JDKProxyImpl();
ProxyInvocationHandler handler = new ProxyInvocationHandler(jdkProxy);
JDKProxy jdkProxy1 = (JDKProxy) Proxy.newProxyInstance(jdkProxy.getClass().getClassLoader(), jdkProxy.getClass().getInterfaces(), handler);
jdkProxy1.parent();
jdkProxy1.child();
System.out.println(jdkProxy1.getClass().getName());
}
}
class ProxyInvocationHandler implements InvocationHandler{
private JDKProxy target;
public ProxyInvocationHandler(JDKProxy target) {
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理方法被执行了。。。");
return method.invoke(target, args);
}
}
操作の後、我々は2つの方法が強化されていることを発見し
、我々は結果が前回と同じであることを当たり前の我々が取るその場合にJDKProxyTest方法で唯一の親を、実行中で親メソッドJDKProxyImplで子を使用する方法。
仮説
現実は
私たちが春の上で、障害のシナリオが同じであることではないでしょうか?シーン春はJDKのダイナミックプロキシまたはCGLIB障害の基礎となるので、この失敗の理由があるように、私たちの春のトランザクションの実装では、達成するためにその上またはCGLIB JDKのダイナミックプロキシ依存しません。
クリアスプリングのトランザクションが無効であるか、これにもちろん、私たちは分析していなかった、のは無駄で仕事を続けてみましょう(JDKプロキシがなぜここ失敗します)
理由JDKエージェントは、平面の詳細な分析」----失敗しました
私たちは、その子()とthis.child()と同等ですが知っている、これで誰がこれを印刷して、それです
、我々は、これはここにfileTest.JDKProxyImplで見ることができます
そして、私たちは親のメソッドを強化し、それを呼び出すために誰によって強化され、
前に、mainメソッドで直接jdkProxy1.parentを実行します(); jdkProxy1.child();強化されましたが、また、プロキシオブジェクトを使用。
まあ、今の親メソッド呼び出しは、子クラスの子方法で失敗し、なぜ我々は最終的に知っている取引を行います。----「JDKのダイナミックプロキシを使用する場合は、プロキシオブジェクトを使用する必要があります。私は春のトランザクションを有効にするようにしたいので、プロキシオブジェクトを使用する必要があります。
だから、どのように我々は、上記の問題を解決するのですか?
- 最も簡単な方法は、単一のクラスを書くことではありません。。。。
- AopContext現在のスレッドから取得
CityServiceImpl proxy=(CityServiceImpl)AopContext.currentProxy();
proxy.child()
- コンテキスト取得したプロキシオブジェクトばねアプリケーション(ApplicationContextのIOCコンテナの単一の実施形態)
@Autowired
private ApplicationContext context;
@PostConstruct
public void init(){
proxy=context.getBean(CityServiceImpl.class);
}
proxy.child()
この方法は、異常なトランザクションのロールバックではありません
SpringのトランザクションAPIドキュメント:
If no rules are relevant to the exception, it will be treated like DefaultTransactionAttribute (rolling back on runtime exceptions).
在业务代码中,有如下两种情况,比如:
throw new RuntimeException(“xxxxxxxxxxxx”); 事务回滚
throw new Exception(“xxxxxxxxxxxx”); 事务没有回滚
spring内部catch的就是 RuntimeException, service抛出RuntimeException可以回滚
如果抛出Exception,就不回滚….
1. Spring的AOP即声明式事务管理默认是针对unchecked exception回滚。
也就是默认对RuntimeException()异常或是其子类进行事务回滚;checked异常,即Exception可try{}捕获的不会回滚,如果使用try-catch捕获抛出的unchecked异常后没有在catch块中采用页面硬编码的方式使用spring api对事务做显式的回滚,则事务不会回滚, “将异常捕获,并且在catch块中不对事务做显式提交=生吞掉异常” ,要想捕获非运行时异常则需要如下配置:
解决办法:
1.在针对事务的类中抛出RuntimeException异常,而不是抛出Exception。
2.在txAdive中增加rollback-for,里面写自己的exception,例如自己写的exception:
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" rollback-for="com.cn.untils.exception.XyzException"/>
</tx:attributes>
</tx:advice>
或者定义不会滚的异常
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="update*" no-rollback-for="IOException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
2. spring的事务边界是在调用业务方法之前开始的,业务方法执行完毕之后来执行commit or rollback(Spring默认取决于是否抛出runtime异常).
如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。
一般不需要在业务方法中catch异常,如果非要catch,在做完你想做的工作后(比如关闭文件等)一定要抛出runtime exception,否则spring会将你的操作commit,这样就会产生脏数据.所以你的catch代码是画蛇添足。
例如:
try {
//bisiness logic code
} catch(Exception e) {
//handle the exception
}
由此可以推知,在spring中如果某个业务方法被一个 整个包裹起来,则这个业务方法也就等于脱离了spring事务的管理,因为没有任何异常会从业务方法中抛出!全被捕获并吞掉,导致spring异常抛出触发事务回滚策略失效。
注:不过,如果在catch代码块中采用页面硬编码的方式使用spring api对事务做显式的回滚,这样写也未尝不可。
3. 基于注解的事务:
Transactional的异常控制,默认是Check Exception 不回滚,unCheck Exception回滚
如果配置了rollbackFor 和 noRollbackFor 且两个都是用同样的异常,那么遇到该异常,还是回滚
rollbackFor 和noRollbackFor 配置也许不会含盖所有异常,对于遗漏的按照Check Exception 不回滚,unCheck Exception回滚
如果只是@Transactional失效的话,可以考虑改成:@Transactional(rollbackFor=Exception.class)
例子如下,在类或方法上的加入:
@Transactional(rollbackFor=Exception.class)