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*teDataSourceexte*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) 收藏 举报
浙公网安备 33010602011771号