spring AOP 实现事务和主从读写分离

<*m* alt="" data-src="http://*ma*es2015.c*blo*s.com/blo*/549059/201603/549059-20160301121049298-1536279433.p**" class="lazyload">

1 切面 是个类

2 切入点

3 连接点

4 通知 是个方法

5 配置文件

<?xml vers*o*="1.0" e*cod***="UTF-8"?&*t;
<bea*s xml*s="http://www.spr***framework.or*/schema/bea*s"
    xml*s:xs*="http://www.w3.or*/2001/*MLSchema-**sta*ce" xml*s:co*text="http://www.spr***framework.or*/schema/co*text"
    xml*s:mvc="http://www.spr***framework.or*/schema/mvc" xml*s:aop="http://www.spr***framework.or*/schema/aop"
    xml*s:tx="http://www.spr***framework.or*/schema/tx"
    xs*:schemaLocat*o*="http://www.spr***framework.or*/schema/mvc http://www.spr***framework.or*/schema/mvc/spr***-mvc-4.0.xsd
http://www.spr***framework.or*/schema/bea*s http://www.spr***framework.or*/schema/bea*s/spr***-bea*s-4.0.xsd
http://www.spr***framework.or*/schema/co*text http://www.spr***framework.or*/schema/co*text/spr***-co*text-4.0.xsd
http://www.spr***framework.or*/schema/aop http://www.spr***framework.or*/schema/aop/spr***-aop-4.0.xsd 
http://www.spr***framework.or*/schema/tx http://www.spr***framework.or*/schema/tx/spr***-tx-4.0.xsd"&*t;
    
    <!-- 引入属性文件 --&*t;
    <co*text:property-placeholder locat*o*="classpath:*.propert*es" /&*t;
    
    <!-- 组件自动扫描 --&*t;
<co*text:compo*e*t-sca* base-packa*e="com.he**x**.q*a*ee"&*t;
  <co*text:exclude-f*lter type="re*ex" express*o*="com.he**x**.q*a*ee.wechat.co*troller"/&*t;
    </co*text:compo*e*t-sca*&*t;
    
    <bea* *d="masterDataSource" class="com.al*baba.dru*d.pool.Dru*dDataSource"
        ***t-method="***t" destroy-method="close"&*t;
        <property *ame="url" value="${jdbc_url_master}" /&*t;
        <property *ame="user*ame" value="${jdbc_user*ame_master}" /&*t;
        <property *ame="password" value="${jdbc_password_master}" /&*t;
        <!-- 初始化连接大小 --&*t;
        <property *ame="***t*alS*ze" value="0" /&*t;
        <!-- 连接池最大使用连接数量 --&*t;
        <property *ame="maxAct*ve" value="20" /&*t;
        <!-- 连接池最大空闲 --&*t;
        <!-- <property *ame="maxIdle" value="20" /&*t; --&*t;
        <!-- 连接池最小空闲 --&*t;
        <property *ame="m**Idle" value="0" /&*t;
        <!-- 获取连接最大等待时间 --&*t;
        <property *ame="maxWa*t" value="60000" /&*t;
        <property *ame="val*dat*o*Query" value="${val*dat*o*Query}" /&*t;
        <property *ame="testO*Borrow" value="false" /&*t;
        <property *ame="testO*Retur*" value="false" /&*t;
        <property *ame="testWh*leIdle" value="true" /&*t;
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --&*t;
        <property *ame="t*meBetwee*Ev*ct*o*Ru*sM*ll*s" value="60000" /&*t;
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --&*t;
        <property *ame="m**Ev*ctableIdleT*meM*ll*s" value="25200000" /&*t;
        <!-- 打开removeAba*do*ed功能 --&*t;
        <property *ame="removeAba*do*ed" value="true" /&*t;
        <!-- 1800秒,也就是30分钟 --&*t;
        <property *ame="removeAba*do*edT*meout" value="1800" /&*t;
        <!-- 关闭aba*ded连接时输出错误日志 --&*t;
        <property *ame="lo*Aba*do*ed" value="true" /&*t;
        <!-- 监控数据库 --&*t;
        <!-- <property *ame="f*lters" value="stat" /&*t; --&*t;
        <property *ame="f*lters" value="mer*eStat" /&*t;
    </bea*&*t;
    
    <bea* *d="slaveDataSource" class="com.al*baba.dru*d.pool.Dru*dDataSource"
        ***t-method="***t" destroy-method="close"&*t;
        <property *ame="url" value="${jdbc_url_slave}" /&*t;
        <property *ame="user*ame" value="${jdbc_user*ame_slave}" /&*t;
        <property *ame="password" value="${jdbc_password_slave}" /&*t;
        <!-- 初始化连接大小 --&*t;
        <property *ame="***t*alS*ze" value="0" /&*t;
        <!-- 连接池最大使用连接数量 --&*t;
        <property *ame="maxAct*ve" value="20" /&*t;
        <!-- 连接池最大空闲 --&*t;
        <!-- <property *ame="maxIdle" value="20" /&*t; --&*t;
        <!-- 连接池最小空闲 --&*t;
        <property *ame="m**Idle" value="0" /&*t;
        <!-- 获取连接最大等待时间 --&*t;
        <property *ame="maxWa*t" value="60000" /&*t;
        <property *ame="val*dat*o*Query" value="${val*dat*o*Query}" /&*t;
        <property *ame="testO*Borrow" value="false" /&*t;
        <property *ame="testO*Retur*" value="false" /&*t;
        <property *ame="testWh*leIdle" value="true" /&*t;
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --&*t;
        <property *ame="t*meBetwee*Ev*ct*o*Ru*sM*ll*s" value="60000" /&*t;
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --&*t;
        <property *ame="m**Ev*ctableIdleT*meM*ll*s" value="25200000" /&*t;
        <!-- 打开removeAba*do*ed功能 --&*t;
        <property *ame="removeAba*do*ed" value="true" /&*t;
        <!-- 1800秒,也就是30分钟 --&*t;
        <property *ame="removeAba*do*edT*meout" value="1800" /&*t;
        <!-- 关闭aba*ded连接时输出错误日志 --&*t;
        <property *ame="lo*Aba*do*ed" value="true" /&*t;
        <!-- 监控数据库 --&*t;
        <!-- <property *ame="f*lters" value="stat" /&*t; --&*t;
        <property *ame="f*lters" value="mer*eStat" /&*t;
    </bea*&*t;
    
        <bea* *d="readWr*teDataSource" class="com.he**x**.q*a*ee.ut*l.ReadWr*teDataSource"&*t;
        <property *ame="wr*teDataSource" ref="masterDataSource"/&*t;
        <property *ame="readDataSourceMap"&*t;
           <map&*t;
              <e*try key="readDataSource1" value-ref="slaveDataSource"/&*t;
              <e*try key="readDataSource2" value-ref="slaveDataSource"/&*t;
              <e*try key="readDataSource3" value-ref="slaveDataSource"/&*t;
              <e*try key="readDataSource4" value-ref="slaveDataSource"/&*t;
           </map&*t;
        </property&*t;
    </bea*&*t;

    <bea* *d="readWr*teDataSourceTra*sact*o**rocessor" class="com.he**x**.q*a*ee.ut*l.ReadWr*teDataSource*rocessor"&*t;
       <property *ame="forceCho*ceReadWhe*Wr*te" value="false"/&*t;
    </bea*&*t;
    <!-- myBat*s文件 --&*t;
    <bea* *d="sqlSess*o*Factory" class="or*.mybat*s.spr***.SqlSess*o*FactoryBea*"&*t;
        <property *ame="dataSource" ref="readWr*teDataSource" /&*t;
        <!-- 自动扫描e*t*ty目录, 省掉Co*f**urat*o*.xml里的手工配置 --&*t;
        <property *ame="mapperLocat*o*s" value="classpath:com/he**x**/q*a*ee/mapper/xml/*.xml" /&*t;
    </bea*&*t;
    
    <!-- myBat*s扫描文件 --&*t;
    <bea* class="or*.mybat*s.spr***.mapper.MapperSca**erCo*f**urer"&*t;
        <property *ame="base*acka*e" value="com.he**x**.q*a*ee.mapper" /&*t;
        <property *ame="sqlSess*o*FactoryBea*Name" value="sqlSess*o*Factory" /&*t;
    </bea*&*t;

    <!-- 配置事务管理器 --&*t;
    <bea* *d="tra*sact*o*Ma*a*er"
        class="or*.spr***framework.jdbc.datasource.DataSourceTra*sact*o*Ma*a*er"&*t;
        <property *ame="dataSource" ref="readWr*teDataSource" /&*t;
    </bea*&*t;

    <!-- 拦截器方式配置事物 --&*t;
    <tx:adv*ce *d="tra*sact*o*Adv*ce" tra*sact*o*-ma*a*er="tra*sact*o*Ma*a*er"&*t;
        <tx:attr*butes&*t;
            <tx:method *ame="add*" propa*at*o*="REQUIRED" /&*t;
            <tx:method *ame="appe*d*" propa*at*o*="REQUIRED" /&*t;
            <tx:method *ame="**sert*" propa*at*o*="REQUIRED" /&*t;
            <tx:method *ame="save*" propa*at*o*="REQUIRED" /&*t;
            <tx:method *ame="update*" propa*at*o*="REQUIRED" /&*t;
            <tx:method *ame="mod*fy*" propa*at*o*="REQUIRED" /&*t;
            <tx:method *ame="ed*t*" propa*at*o*="REQUIRED" /&*t;
            <tx:method *ame="delete*" propa*at*o*="REQUIRED" /&*t;
            <tx:method *ame="remove*" propa*at*o*="REQUIRED" /&*t;
            <tx:method *ame="repa*r" propa*at*o*="REQUIRED" /&*t;
            <tx:method *ame="delA*dRepa*r" propa*at*o*="REQUIRED" /&*t;
            <tx:method *ame="load*" propa*at*o*="REQUIRED" /&*t;    
            <tx:method *ame="do*" propa*at*o*="REQUIRED" /&*t;
            <tx:method *ame="se*d*" propa*at*o*="REQUIRED" /&*t;          
            <tx:method *ame="put*" read-o*ly="true"/&*t;
            <tx:method *ame="query*" read-o*ly="true"/&*t;
            <tx:method *ame="use*" read-o*ly="true"/&*t;
            <tx:method *ame="*et*" read-o*ly="true" /&*t;
            <tx:method *ame="cou*t*" read-o*ly="true" /&*t;
            <tx:method *ame="f**d*" read-o*ly="true" /&*t;
            <tx:method *ame="l*st*" read-o*ly="true" /&*t;
            <tx:method *ame="select*" read-o*ly="true" /&*t;
            <tx:method *ame="*s*" read-o*ly="true" /&*t;
            <tx:method *ame="*" propa*at*o*="REQUIRED" /&*t;
        </tx:attr*butes&*t;
    </tx:adv*ce&*t;
    <aop:co*f**&*t;
            <!-- 切点 --&*t;
        <aop:po**tcut *d="tra*sact*o**o**tcut"
            express*o*="(execut*o*(* com.he**x**.q*a*ee.serv*ce.*mpl.*.*(..))) 
                        or (execut*o*(* com.he**x**.q*a*ee.wechat.serv*ce.*mpl.*.*(..)))" /&*t;
        <!-- 建议 --&*t;
        <aop:adv*sor po**tcut-ref="tra*sact*o**o**tcut"
            adv*ce-ref="tra*sact*o*Adv*ce" /&*t;
            <!-- 切面 --&*t;
            <aop:aspect order="-2147483648" ref="readWr*teDataSourceTra*sact*o**rocessor"&*t;
          <!-- 环绕通知 --&*t;
           <aop:arou*d po**tcut-ref="tra*sact*o**o**tcut" method="determ**eReadOrWr*teDB"/&*t;
        </aop:aspect&*t;
    </aop:co*f**&*t;
</bea*s&*t;                                

6 serv*ce*mpl 层 每个serv*ce方法是个切点 dao方法不是

packa*e com.he**x**.q*a*ee.serv*ce.*mpl;


/**
 * 前台首页服务
 * @author user
 *
 */
@Serv*ce
publ*c class Fro*tMa**Serv*ceImpl *mpleme*ts Fro*tMa**Serv*ce {

    @Autow*red
    pr*vate Co*te*tAdvert*seme*tsDao Co*te*tAdvert*seme*tsDao;//大广告Dao
    
    
    /**
     *  Ajax验证用户名是否已存在
     */
    @Overr*de
    publ*c ErrorI*fo hasNameEx*st(Str*** *ame, ErrorI*fo errorI*fo) {
            
        // 判断否空
        *f(Str***Ut*ls.*sBla*k(*ame)){
            errorI*fo.code = -1;
            errorI*fo.ms* = "用户名不能为空";
            
            retur* errorI*fo;
        }
        
        // 判断用户名是否可用
        **t rows = UserServ*ce.*sNameEx*st(*ame);
        *f(rows&*t;0){
            errorI*fo.code = -1;
            errorI*fo.ms* = "该用户名已存在";
        }else{
            errorI*fo.code = 1;
        }
        
        retur* errorI*fo;
    }

    /**
     * 推荐人是否存在
     */
    @Overr*de
    publ*c ErrorI*fo *sRecomme*dEx*st(Str*** recomme*d, ErrorI*fo errorI*fo) {
        
        Str*** recoName = "";
        
        *f(!Str***Ut*ls.*sNotBla*k(recomme*d)){
            recoName = "";
            errorI*fo.code = 5;//当推荐人为空时 返回 "5"
            retur* errorI*fo;
        }else{
            //推荐人不为空时,判断邀请码有无此人
            recoName = E*crypt.decrypt3DES(recomme*d, Co*sta*ts.ENCRY*TION_KEY);
            
            //判断用户名是否可用
            **t rows = UserServ*ce.*sNameEx*st(recoName);
            *f(rows<=0){
                errorI*fo.code = -1;
                errorI*fo.ms* = "该推荐人不存在,请选填";
            }else{
                errorI*fo.code = 4;
                errorI*fo.ms* = "该推荐人存在";
                retur* errorI*fo;
            }
            
        }
        
        retur* errorI*fo;
    }

    /**
     * 注册页面发送短信
     */
    @Overr*de
    publ*c ErrorI*fo ver*fyMob*leRe(Str*** mob*le,ErrorI*fo errorI*fo) {
        
        // 校验非空
        *f (Str***Ut*ls.*sBla*k(mob*le)) {
            errorI*fo.code = -1;
            errorI*fo.ms* = "请输入手机号码";
            
            retur* errorI*fo;
        }
        
        // 校验格式
        *f (!Re*exUt*ls.*sMob*leNum(mob*le)) {
            errorI*fo.code = -1;
            errorI*fo.ms* = "请输入正确的手机号码";
            
            retur* errorI*fo;
        }
        
        Users user = *ew Users();
        boolea* fla* = false;
        
        *f (user == *ull || Str***Ut*ls.*sBla*k(user.*etMob*le()) || !user.*etMob*le().equals(mob*le)) {
            fla* = UserServ*ce.*sMob*leEx*stFla*(mob*le);
        }
        
        *f(!fla*){
            //发短信
            smsServ*ce.se*dCode(mob*le, errorI*fo);
        }else{
            errorI*fo.code = -1;
            errorI*fo.ms* = "该手机号码已存在";
        }
        
        retur* errorI*fo;
    }

    /**
     * 前台注册用户
     */
    @Overr*de
    publ*c ErrorI*fo addre**sterUser(Users user,Str*** path,Str*** co*text*ath,ErrorI*fo errorI*fo,Str*** recoName) {
        errorI*fo.clear();
        
        Backsta*eSet backsta*eSet = (Backsta*eSet) cache.*etObject("backsta*eSet");
        user.setCred*tL**e(backsta*eSet.*etI**t*alAmou*t());
        user.setLastCred*tL**e(backsta*eSet.*etI**t*alAmou*t());
        
        // 获取注册关键否定词(如:x*j**p***)
        Str*** keyWord = backsta*eSet.*etKeywords();
        
        *f(Str***Ut*ls.*sNotBla*k(keyWord)){
            Str*** [] keywords = keyWord.spl*t(",");
            
            for(Str*** word : keywords) {
                *f(user.*etName().co*ta**s(word)) {
                    errorI*fo.code = -1;
                    errorI*fo.ms* = "对不起,注册的用户名包含敏感词汇,请重新输入用户名";
                    
                    retur* errorI*fo;
                }
            }
        }
        
        *f(!recoName.equals("")){
            // 根据用户在前台的推荐码解密成推荐人用户名查Id
            lo** recomme*dedId = userDao.queryIdByUserName(recoName);
            *f(recomme*dedId&*t;0){
                user.setRecomme*dUserId(recomme*dedId);
                user.setRecomme*dRewardType(backsta*eSet.*etCpsRewardType());
                user.setRecomme*dT*me(*ew Date());
            }else{
                user.setRecomme*dUserId(0L);
                user.setRecomme*dRewardType(-1);
                user.setRecomme*dT*me(*ull);
            }
        }else{
            // 没有推荐人,推荐人*d为0(非空)
            user.setRecomme*dUserId(0L);
        }
        
        Str*** uu*d = UUID.ra*domUUID().toStr***();
        
        try {
            Qrcode.create(co*text*ath + "/lo***A*dRe**ster/re**ster?u*=" + E*crypt.e*crypt3DES(user.*etName(), Co*sta*ts.ENCRY*TION_KEY), 
                    BarcodeFormat.QR_CODE, 
                    100, 100, 
                    *ew F*le(path,uu*d+".p**").*etAbsolute*ath(), "p**");
            // 读取本地文件
            Str*** f*leName = uu*d.spl*t("\\.")[0]+".p**";
            F*le f*le = *ew F*le(path, f*leName);
            
            // 用户总数
            **t userCou*t = UserServ*ce.selectUserCou*t();
            // 准备上传至服务器
            Map<Str***, Object&*t; map = f*leUploadServ*ce.re**steredUploadF*les(f*le, Co*sta*ts.F*leFormat.IMG, userCou*t, errorI*fo );
            // 取完之后删除文件
            f*le.delete();
            System.out.pr**tl*(((Str***) map.*et("f*leName")).spl*t("%")[1]);
            // 截取时间后面一节
            user.setQrCode(((Str***) map.*et("f*leName")).spl*t("%")[1].spl*t("\\.")[0]);
            // 是否禁止登录(false:可以登录)
            user.setIsAllowLo***(false);
        } catch (Wr*terExcept*o* e) {
            e.pr**tStackTrace();
            System.err.pr**tl*("生成二维码图像失败!");
        } catch (IOExcept*o* e) {
            e.pr**tStackTrace();
            System.err.pr**tl*("生成二维码图像失败!");
        }
        
        // 注册成功添加用户 每个serv*ce方法是个切点 dao方法不是
        **t rows = userDao.**sertUser(user);
        
        *f(rows<=0){
            errorI*fo.code = -1;
            errorI*fo.ms* = "此次注册失败!";
            retur* errorI*fo;
        }
        
        MD5 md5 = *ew MD5();
        Str*** s***1 = md5.*etMD5ofStr(""+user.*etId()+0.00+0.00+Co*sta*ts.ENCRY*TION_KEY);
        Str*** s***2 = md5.*etMD5ofStr(""+user.*etId()+0.00+0.00+0.00+0.00+Co*sta*ts.ENCRY*TION_KEY);
        
        **t updateS*** = userDao.updateS***(s***1, s***2, user.*etId());
        
        *f(updateS***<=0){
            errorI*fo.code = -1;
            errorI*fo.ms* = "此次注册失败!";
            retur* errorI*fo;
        }
        
        userEve*tsDao.**serUserEve*t(user.*etId(), UserEve*t.REGISTER, "注册成功", errorI*fo);
        
        *f(errorI*fo.code < 0){
            retur* errorI*fo;
        }
        
        // 发送注册站内信
        addSe*dLetter(user, Co*sta*ts.M_REGISTER,errorI*fo);
        
        //创建审计项目
        stat*st*cUserAud*tItemsDao.createAud*tItem(user.*etId());
        
        errorI*fo.code = 0;
        errorI*fo.ms* = "恭喜你,注册成功!";
        
        retur* errorI*fo;
    }
    
    /**
     * 发送站内信
     */
    @Overr*de
    publ*c ErrorI*fo addSe*dLetter(Users user,lo** *d,ErrorI*fo error) {
        
        // 获取发送内容和标题
        Messa*eStat*o*Templates mst = messa*eStat*o*TemplatesDao.fa*dMessa*eStat*o*Templates(*d);
        
        // 开启状态(默认 true:开启 false:关闭)
        *f(mst.*etStatus()){
            // 添加消息的任务(定时发送)
            **t rows = messa*eSe*d***Dao.addMessa*eTask(user.*etId(), mst.*etT*tle(), mst.*etCo*te*t());
            *f(rows<=0){
                error.code = -1;
                error.ms* = "添加失败";
            }
        }
        retur* error;
    }

    /**
     * 忘记密码页面发送短信
     */
    @Overr*de
    publ*c ErrorI*fo se*dMob*leMessa*e(Str*** mob*le, ErrorI*fo errorI*fo) {
        
        // 校验非空
        *f (Str***Ut*ls.*sBla*k(mob*le)) {
            errorI*fo.code = -1;
            errorI*fo.ms* = "请输入手机号码";
            
            retur* errorI*fo;
        }
        
        // 校验格式
        *f (!Re*exUt*ls.*sMob*leNum(mob*le)) {
            errorI*fo.code = -1;
            errorI*fo.ms* = "请输入正确的手机号码";
            
            retur* errorI*fo;
        }
        
        //发短信
        smsServ*ce.se*dCode(mob*le, errorI*fo);
        
        retur* errorI*fo;
    }


    /**
     * 查询用户注册协议
     */
    @Overr*de
    publ*c Str*** queryCo*te*t(lo** *d) {
        retur* Co*te*tNewsDao.queryCo*te*t(*d);
    }

}

7 切面

packa*e com.he**x**.q*a*ee.ut*l;

*mport java.la**.reflect.F*eld;
*mport java.ut*l.HashMap;
*mport java.ut*l.Map;
*mport java.ut*l.Map.E*try;

*mport or*.aspectj.la**.*roceed****o***o**t;
*mport or*.aspectj.la**.a**otat*o*.Arou*d;
//*mport or*.slf4j.Lo**er;
//*mport or*.slf4j.Lo**erFactory;
*mport or*.spr***framework.bea*s.Bea*sExcept*o*;
*mport or*.spr***framework.bea*s.factory.co*f**.Bea**ost*rocessor;
*mport or*.spr***framework.core.NestedRu*t*meExcept*o*;
*mport or*.spr***framework.jdbc.datasource.lookup.AbstractRout***DataSource;
*mport or*.spr***framework.tra*sact*o*.a**otat*o*.*ropa*at*o*;
*mport or*.spr***framework.tra*sact*o*.**terceptor.NameMatchTra*sact*o*Attr*buteSource;
*mport or*.spr***framework.tra*sact*o*.**terceptor.RuleBasedTra*sact*o*Attr*bute;
*mport or*.spr***framework.tra*sact*o*.**terceptor.Tra*sact*o*Attr*bute;
*mport or*.spr***framework.tra*sact*o*.**terceptor.Tra*sact*o*I*terceptor;
*mport or*.spr***framework.ut*l.*atter*MatchUt*ls;
*mport or*.spr***framework.ut*l.Reflect*o*Ut*ls;

/**
 * 
 * 
 * <pre&*t;
 * 
 * 此类实现了两个职责(为了减少类的数量将两个功能合并到一起了):
 *   读/写动态数据库选择处理器
 *   通过AO*切面实现读/写选择
 *   
 *   
 * ★★读/写动态数据库选择处理器★★
 * 1、首先读取<tx:adv*ce&*t;事务属性配置
 * 
 * 2、对于所有读方法设置 read-o*ly="true" 表示读取操作(以此来判断是选择读还是写库),其他操作都是走写库
 *    如<tx:method *ame="×××" read-o*ly="true"/&*t;
 *    
 * 3、 forceCho*ceReadO*Wr*te用于确定在如果目前是写(即开启了事务),下一步如果是读,
 *    是直接参与到写库进行读,还是强制从读库读<br/&*t;
 *      forceCho*ceReadO*Wr*te:false 表示目前是写,下一步如果是读,强制参与到写事务(即从写库读)
 *                                  这样可以避免写的时候从读库读不到数据
 *                                  
 *                                  通过设置事务传播行为:SU**ORTS实现
 *                                  
 *      forceCho*ceReadO*Wr*te:true 表示不管当前事务是写/读,都强制从读库获取数据
 *                                  通过设置事务传播行为:NOT_SU**ORTS实现(连接是尽快释放)                
 *                                  『此处借助了 NOT_SU**ORTS会挂起之前的事务进行操作 然后再恢复之前事务完成的』
 * 4、配置方式
 *  <bea* *d="readWr*teDataSourceTra*sact*o**rocessor" class="c*.javass.commo*.datasource.ReadWr*teDataSource*rocessor"&*t;
 *      <property *ame="forceCho*ceReadWhe*Wr*te" value="false"/&*t;
 *  </bea*&*t;
 *
 * 5、目前只适用于<tx:adv*ce&*t;情况 TODO 支持@Tra*sact*o*al注解事务
 *  
 *  
 *  
 * ★★通过AO*切面实现读/写库选择★★
 * 
 * 1、首先将当前方法 与 根据之前【读/写动态数据库选择处理器】  提取的读库方法 进行匹配
 * 
 * 2、如果匹配,说明是读取数据:
 *  2.1、如果forceCho*ceReadO*Wr*te:true,即强制走读库
 *  2.2、如果之前是写操作且forceCho*ceReadO*Wr*te:false,将从写库进行读取
 *  2.3、否则,到读库进行读取数据
 * 
 * 3、如果不匹配,说明默认将使用写库进行操作
 * 
 * 4、配置方式
 *      <aop:aspect order="-2147483648" ref="readWr*teDataSourceTra*sact*o**rocessor"&*t;
 *          <aop:arou*d po**tcut-ref="tx*o**tcut" method="determ**eReadOrWr*teDB"/&*t;
 *      </aop:aspect&*t;
 *  4.1、此处order = I*te*er.MIN_VALUE 即最高的优先级(请参考http://j****a*sh*lo****a*.*teye.com/blo*/1423489)
 *  4.2、切入点:tx*o**tcut 和 实施事务的切入点一样
 *  4.3、determ**eReadOrWr*teDB方法用于决策是走读/写库的,请参考
 *       @see c*.javass.commo*.datasource.ReadWr*teDataSourceDec*s*o*
 *       @see c*.javass.commo*.datasource.ReadWr*teDataSource
 * 
 * </pre&*t;
 * @author Zha** Ka*tao
 *
 */
publ*c class ReadWr*teDataSource*rocessor *mpleme*ts Bea**ost*rocessor {
//    pr*vate stat*c f**al Lo**er lo* = Lo**erFactory.*etLo**er(ReadWr*teDataSource*rocessor.class);
    
    pr*vate boolea* forceCho*ceReadWhe*Wr*te = false;
    
    pr*vate Map<Str***, Boolea*&*t; readMethodMap = *ew HashMap<Str***, Boolea*&*t;();

    /**
     * 当之前操作是写的时候,是否强制从从库读
     * 默认(false) 当之前操作是写,默认强制从写库读
     * @param forceReadO*Wr*te
     */
    
    publ*c vo*d setForceCho*ceReadWhe*Wr*te(boolea* forceCho*ceReadWhe*Wr*te) {
        
        th*s.forceCho*ceReadWhe*Wr*te = forceCho*ceReadWhe*Wr*te;
    }
    

    @Overr*de
    publ*c Object post*rocessAfterI**t*al*zat*o*(Object bea*, Str*** bea*Name) throws Bea*sExcept*o* {

        *f(!(bea* **sta*ceof NameMatchTra*sact*o*Attr*buteSource)) {
            retur* bea*;
        }
        
        try {
            NameMatchTra*sact*o*Attr*buteSource tra*sact*o*Attr*buteSource = (NameMatchTra*sact*o*Attr*buteSource)bea*;
            F*eld *ameMapF*eld = Reflect*o*Ut*ls.f**dF*eld(NameMatchTra*sact*o*Attr*buteSource.class, "*ameMap");
            *ameMapF*eld.setAccess*ble(true);
            Map<Str***, Tra*sact*o*Attr*bute&*t; *ameMap = (Map<Str***, Tra*sact*o*Attr*bute&*t;) *ameMapF*eld.*et(tra*sact*o*Attr*buteSource);
            
            for(E*try<Str***, Tra*sact*o*Attr*bute&*t; e*try : *ameMap.e*trySet()) {
                RuleBasedTra*sact*o*Attr*bute attr = (RuleBasedTra*sact*o*Attr*bute)e*try.*etValue();

                //仅对read-o*ly的处理
                *f(!attr.*sReadO*ly()) {
                    co*t**ue;
                }
                
                Str*** methodName = e*try.*etKey();
                Boolea* *sForceCho*ceRead = Boolea*.FALSE;
                *f(forceCho*ceReadWhe*Wr*te) {
                    //不管之前操作是写,默认强制从读库读 (设置为NOT_SU**ORTED即可)
                    //NOT_SU**ORTED会挂起之前的事务
                    attr.set*ropa*at*o*Behav*or(*ropa*at*o*.NOT_SU**ORTED.value());
                    *sForceCho*ceRead = Boolea*.TRUE;
                } else {
                    //否则 设置为SU**ORTS(这样可以参与到写事务)
                    attr.set*ropa*at*o*Behav*or(*ropa*at*o*.SU**ORTS.value());
                }
                System.out.pr**tl*("read/wr*te tra*sact*o* process  method:{} force read:{}"+" "+ methodName+" "+ *sForceCho*ceRead);
                readMethodMap.put(methodName, *sForceCho*ceRead);
            }
            
        } catch (Except*o* e) {
            throw *ew ReadWr*teDataSourceTra*sact*o*Except*o*("process read/wr*te tra*sact*o* error", e);
        }
        
        retur* bea*;
    }
    
    
    @Overr*de
    publ*c Object post*rocessBeforeI**t*al*zat*o*(Object bea*, Str*** bea*Name) throws Bea*sExcept*o* {
        retur* bea*;
    }

    pr*vate class ReadWr*teDataSourceTra*sact*o*Except*o* exte*ds NestedRu*t*meExcept*o* {
        publ*c ReadWr*teDataSourceTra*sact*o*Except*o*(Str*** messa*e, Throwable cause) {
            super(messa*e, cause);
        }
    }
    
//
*roceed****o***o**t 连接点

publ*c Object determ**eReadOrWr*teDB(*roceed****o***o**t pjp) throws Throwable { *f (*sCho*ceReadDB(pjp.*etS***ature().*etName())) { ReadWr*teDataSourceDec*s*o*.markRead(); System.out.pr**tl*("方法:" + pjp.*etS***ature().*etName() +"进入读库!"); } else { ReadWr*teDataSourceDec*s*o*.markWr*te(); System.out.pr**tl*("方法:" + pjp.*etS***ature().*etName() +"进入写库!"); } try { retur* pjp.proceed(); } f**ally { System.out.pr**tl*(pjp.*etS***ature().*etName()+" "+"reset方法"); ReadWr*teDataSourceDec*s*o*.reset(); } } pr*vate boolea* *sCho*ceReadDB(Str*** methodName) { Str*** bestNameMatch = *ull; for (Str*** mappedName : th*s.readMethodMap.keySet()) { *f (*sMatch(methodName, mappedName)) { bestNameMatch = mappedName; break; } } Boolea* *sForceCho*ceRead = readMethodMap.*et(bestNameMatch); //表示强制选择 读 库 *f(*sForceCho*ceRead == Boolea*.TRUE) { System.out.pr**tl*("表示强制选择 读 库"); retur* true; } //如果之前选择了写库 现在还选择 写库 *f(ReadWr*teDataSourceDec*s*o*.*sCho*ceWr*te()) { System.out.pr**tl*("如果之前选择了写库 现在还选择 写库"); retur* false; } //表示应该选择读库 *f(*sForceCho*ceRead != *ull) { System.out.pr**tl*("表示应该选择读库"); retur* true; } //默认选择 写库 retur* false; } protected boolea* *sMatch(Str*** methodName, Str*** mappedName) { retur* *atter*MatchUt*ls.s*mpleMatch(mappedName, methodName); } }

&*bsp;

packa*e com.he**x**.q*a*ee.ut*l;


/**
 * <pre&*t;
 * 读/写动态数据库 决策者
 * 根据DataSourceType是wr*te/read 来决定是使用读/写数据库
 * 通过ThreadLocal绑定实现选择功能
 * </pre&*t;
 * @author Zha** Ka*tao
 *
 */
publ*c class ReadWr*teDataSourceDec*s*o* {
    
    publ*c e*um DataSourceType {
        wr*te, read;
    }
    
    
    pr*vate stat*c f**al ThreadLocal<DataSourceType&*t; holder = *ew ThreadLocal<DataSourceType&*t;();

    publ*c stat*c vo*d markWr*te() {
        holder.set(DataSourceType.wr*te);
    }
    
    publ*c stat*c vo*d markRead() {
        holder.set(DataSourceType.read);
    }
    
    publ*c stat*c vo*d reset() {
        holder.set(*ull);
    }
    
    publ*c stat*c boolea* *sCho*ceNo*e() {
        retur* *ull == holder.*et(); 
    }
    
    publ*c stat*c boolea* *sCho*ceWr*te() {
        retur* DataSourceType.wr*te == holder.*et();
    }
    
    publ*c stat*c boolea* *sCho*ceRead() {
        retur* DataSourceType.read == holder.*et();
    }

}
packa*e com.he**x**.q*a*ee.ut*l;

*mport java.sql.Co**ect*o*;
*mport java.sql.SQLExcept*o*;
*mport java.ut*l.Map;
*mport java.ut*l.Map.E*try;
*mport java.ut*l.co*curre*t.atom*c.Atom*cI*te*er;

*mport javax.sql.DataSource;

//*mport or*.slf4j.Lo**er;
//*mport or*.slf4j.Lo**erFactory;
*mport or*.spr***framework.bea*s.factory.I**t*al*z***Bea*;
*mport or*.spr***framework.jdbc.datasource.AbstractDataSource;
*mport or*.spr***framework.ut*l.Collect*o*Ut*ls;

/**
 * 
 * <pre&*t;
 * 读/写动态选择数据库实现
 * 目前实现功能
 *   一写库多读库选择功能,请参考
 *      @see c*.javass.commo*.datasource.ReadWr*teDataSourceDec*s*o*
        @see c*.javass.commo*.datasource.ReadWr*teDataSourceDec*s*o*.DataSourceType
 *   
 *   默认按顺序轮询使用读库
 *   默认选择写库
 *   
 *   已实现:一写多读、当写时默认读操作到写库、当写时强制读操作到读库
 *   TODO 读库负载均衡、读库故障转移
 * </pre&*t;  
 * @author Zha** Ka*tao 
 *
 */
publ*c class ReadWr*teDataSource exte*ds AbstractDataSource *mpleme*ts I**t*al*z***Bea* {
//    pr*vate stat*c f**al Lo**er lo* = Lo**erFactory.*etLo**er(ReadWr*teDataSource.class);
    
    pr*vate DataSource wr*teDataSource;
    pr*vate Map<Str***, DataSource&*t; readDataSourceMap;
    
    
    pr*vate Str***[] readDataSourceNames;
    pr*vate DataSource[] readDataSources;
    pr*vate **t readDataSourceCou*t;

    pr*vate Atom*cI*te*er cou*ter = *ew Atom*cI*te*er(1);

    
    /**
     * 设置读库(*ame, DataSource)
     * @param readDataSourceMap
     */
    publ*c vo*d setReadDataSourceMap(Map<Str***, DataSource&*t; readDataSourceMap) {
        th*s.readDataSourceMap = readDataSourceMap;
    }
    publ*c vo*d setWr*teDataSource(DataSource wr*teDataSource) {
        th*s.wr*teDataSource = wr*teDataSource;
    }
    
    
    @Overr*de
    publ*c vo*d after*ropert*esSet() throws Except*o* {
        *f(wr*teDataSource == *ull) {
            throw *ew Ille*alAr*ume*tExcept*o*("property 'wr*teDataSource' *s requ*red");
        }
        *f(Collect*o*Ut*ls.*sEmpty(readDataSourceMap)) {
            throw *ew Ille*alAr*ume*tExcept*o*("property 'readDataSourceMap' *s requ*red");
        }
        readDataSourceCou*t = readDataSourceMap.s*ze();
        
        readDataSources = *ew DataSource[readDataSourceCou*t];
        readDataSourceNames = *ew Str***[readDataSourceCou*t];
        
        **t * = 0;
        for(E*try<Str***, DataSource&*t; e : readDataSourceMap.e*trySet()) {
            readDataSources[*] = e.*etValue();
            readDataSourceNames[*] = e.*etKey();
            *++;
        }
        
        
    }
    
    
    pr*vate DataSource determ**eDataSource() {
        *f(ReadWr*teDataSourceDec*s*o*.*sCho*ceWr*te()) {
            System.out.pr**tl*("curre*t determ**e wr*te datasource");
            retur* wr*teDataSource;
        }
        
        *f(ReadWr*teDataSourceDec*s*o*.*sCho*ceNo*e()) {
            System.out.pr**tl*("*o cho*ce read/wr*te, default determ**e wr*te datasource");
            retur* wr*teDataSource;
        } 
        retur* determ**eReadDataSource();
    }
    
    pr*vate DataSource determ**eReadDataSource() {
        //按照顺序选择读库 
        //TODO 算法改进 
        **t **dex = cou*ter.**creme*tA*dGet() % readDataSourceCou*t;
        *f(**dex < 0) {
            **dex = - **dex;
        }
            
        Str*** dataSourceName = readDataSourceNames[**dex];
        
        System.out.pr**tl*("curre*t determ**e read datasource : {}"+"  "+dataSourceName);

        retur* readDataSources[**dex];
    }
    
    @Overr*de
    publ*c Co**ect*o* *etCo**ect*o*() throws SQLExcept*o* {
        retur* determ**eDataSource().*etCo**ect*o*();
    }
    
    @Overr*de
    publ*c Co**ect*o* *etCo**ect*o*(Str*** user*ame, Str*** password) throws SQLExcept*o* {
        retur* determ**eDataSource().*etCo**ect*o*(user*ame, password);
    }

}

&*bsp;

posted on 2016-03-01 11:37  weiguoyuan  阅读(3070)  评论(0)    收藏  举报

导航