博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Play 1.x框架学习之七:多数据库切换与源码修改 ( Databases Switch And Modify Source Code)...
阅读量:6171 次
发布时间:2019-06-21

本文共 10117 字,大约阅读时间需要 33 分钟。

  hot3.png

在单数据源(单个ip)下的多库,可以使用use xxdb 命令进行切换,但是如果多个数据源的情况下,use命令就不行了。在play框架中,提供了多数据源多库的切换。本文不提供完全的例子,只提供部分的代码,而且重点是在后面的修改源码。 如果需要多ip多库切换,就必须有一个主库,保存所有分库的信息。就如云应用中,需要保存所有租户的数据源与库名,因为可能是多个库共用一个服务器,多个服务器构成集群云应用。 首先需要配置数据源,其中有主库的db与分库db_01: Conf/application.conf

jpa.dialect=org.hibernate.dialect.MySQLDialectdb.url=jdbc:mysql://basedbip:3306/pop?autoReconnect=true&useUnicode=true&characterEncoding=utf-8db.driver=com.mysql.jdbc.Driverdb.user=rootdb.pass=rootdb=popdb_01.url=jdbc:mysql://anotherdbip:3306/pop?autoReconnect=true&useUnicode=true&characterEncoding=utf-8db_01.driver=com.mysql.jdbc.Driverdb_01.user=rootdb_01.pass=rootdb_01=pop01

切库的方法如下: Util/DB.java

package util;import play.db.jpa.JPA;public class DB {    public static void changeDB(String dbconfigname, String dbname){        try {            JPA.setCurrentConfigName(dbconfigname);            if (JPA.em().getTransaction().isActive() == false) {                JPA.em().getTransaction().begin();            }            JPA.em().createNativeQuery("use "+dbname).executeUpdate();        } catch (Exception e) {            e.printStackTrace();        }    }}

完成上述配置后,可以进行自由切库,但是如果你实际运行中,却出现一个重大问题。就是在controller层涉及数据封装,如:修改用户:

public static void update(@Required @Valid User user){    user.save();    renderText(user.id);}

在封装user对象参数的时候,play1.x框架的自动查询数据库,这个处理是在controller方法之前,所以根本无法切库!因为主库与分库表与数据不同,所以会报错!

play.exceptions.UnexpectedException: Unexpected Error    at play.data.validation.ValidationPlugin.beforeActionInvocation(ValidationPlugin.java:59)    at play.plugins.PluginCollection.beforeActionInvocation(PluginCollection.java:518)    at play.mvc.ActionInvoker.invoke(ActionInvoker.java:131)    at Invocation.HTTP Request(Play!)Caused by: play.exceptions.UnexpectedException: Unexpected Error    at play.db.jpa.JPAPlugin.bind(JPAPlugin.java:67)    at play.plugins.PluginCollection.bind(PluginCollection.java:468)    at play.data.binding.Binder.bind(Binder.java:309)    at play.mvc.ActionInvoker.getActionMethodArgs(ActionInvoker.java:621)    at play.data.validation.ValidationPlugin$Validator.validateAction(ValidationPlugin.java:95)    at play.data.validation.ValidationPlugin.beforeActionInvocation(ValidationPlugin.java:51)    ... 3 moreCaused by: javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not execute query    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1214)    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1147)    at org.hibernate.ejb.QueryImpl.getSingleResult(QueryImpl.java:307)    at play.db.jpa.JPAPlugin.bind(JPAPlugin.java:62)    ... 8 moreCaused by: org.hibernate.exception.SQLGrammarException: could not execute query    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:92)    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)    at org.hibernate.loader.Loader.doList(Loader.java:2536)    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2276)    at org.hibernate.loader.Loader.list(Loader.java:2271)    at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:452)    at org.hibernate.hql.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:363)    at org.hibernate.engine.query.HQLQueryPlan.performList(HQLQueryPlan.java:196)    at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1268)    at org.hibernate.impl.QueryImpl.list(QueryImpl.java:102)    at org.hibernate.ejb.QueryImpl.getSingleResult(QueryImpl.java:274)    ... 9 moreCaused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ´pop.user´ doesn´t exist    at com.mysql.jdbc.Util.handleNewInstance(Util.java:407)    at com.mysql.jdbc.Util.getInstance(Util.java:382)    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1052)    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3593)    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3525)    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1986)    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2140)    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2626)    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2111)    at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:2273)    at org.hibernate.jdbc.AbstractBatcher.getResultSet(AbstractBatcher.java:208)    at org.hibernate.loader.Loader.getResultSet(Loader.java:1953)    at org.hibernate.loader.Loader.doQuery(Loader.java:802)    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)    at org.hibernate.loader.Loader.doList(Loader.java:2533)    ... 17 more

看到错误栈最后一条信息,说表不存在! 可能你想在父类控制器controller利用@before标签,统一在方法调用前,进行拦截配置。但是结果会让你失望!错误会依旧! 这时候我们需要根据报错信息,深入play框架源码。 从caused by中看到JPAPlugin.java,可是已经用到JPA查询数据库了。在第二个caused by中 JPAPlugin.java:62 处可以看到jpaplugin的bind方法如下:

@Override@SuppressWarnings("unchecked")public Object bind(String name, Class clazz, java.lang.reflect.Type type, Annotation[] annotations, Map params) {    // TODO need to be more generic in order to work with JPASupport    if (JPABase.class.isAssignableFrom(clazz)) {        String keyName = Model.Manager.factoryFor(clazz).keyName();        String idKey = name + "." + keyName;        if (params.containsKey(idKey) && params.get(idKey).length > 0 && params.get(idKey)[0] != null && params.get(idKey)[0].trim().length() > 0) {            String id = params.get(idKey)[0];            try {                Query query = JPA.em().createQuery("from " + clazz.getName() + " o where o." + keyName + " = ?");                query.setParameter(1, play.data.binding.Binder.directBind(name, annotations, id + "", Model.Manager.factoryFor(clazz).keyType()));                Object o = query.getSingleResult();                return GenericModel.edit(o, name, params, annotations);            } catch (NoResultException e) {                // ok            } catch (Exception e) {                throw new UnexpectedException(e);            }        }        return GenericModel.create(clazz, name, params, annotations);    }    return super.bind(name, clazz, type, annotations, params);}

一目了然,方法中第70行

Query query = JPA.em().createQuery("from " + clazz.getName() + " o where o." + keyName + " = ?");

是在根据主键查询数据表中数据,但是这个时候还没有有切到相应的分库,所以是没有要查询表的!(就算主库中相应的表,也没有相应的数据)。 为什么说没有切库呢?不是用@before处理了吗?我们继续追寻错误信息到ActionInvoker.java 中的invoke方法,代码太多,只贴出错误提示的131行附近

ControllerInstrumentation.stopActionCall();        Play.pluginCollection.beforeActionInvocation(actionMethod);        // Monitoring         monitor = MonitorFactory.start(request.action + "()");        // 3. Invoke the action        try {            // @Before            handleBefores(request);            // Action            Result actionResult = null;            String cacheKey = null;

在此处

Play.pluginCollection.beforeActionInvocation(actionMethod);

play框架处理了一系列插件调用前的准备工作,包括redisplugin/jpaplugin/validationplugin/wsplugin等等,其中的validationplugin就是处理所有有校验注解的实体类,同时也包括打了@id注解的主键而处理@before的方法在后面的

handleBefores(request);

所以说 用befores也是无用的! 这里一个可行的方案是修改play源码,仿照befores注解的处理,在play.mvc下添加一个BeforeValidation注解,在校验之前进行切库!

play.mvc.BeforeValidation.javapackage play.mvc;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Mark this method as @BeforeValidation interceptor */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface BeforeValidation {         /**     * Does not intercept these actions     */    String[] unless() default {};    String[] only() default {};    /**     * Interceptor priority (0 is high priority)     */    int priority() default 0; }

同时在ActionInvoker中添加方法handleBeforeValidations play.mvc.ActionInvoker.java

private static void handleBefores(Http.Request request) throws Exception {        List befores = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Before.class);        Collections.sort(befores, new Comparator() {            public int compare(Method m1, Method m2) {                Before before1 = m1.getAnnotation(Before.class);                Before before2 = m2.getAnnotation(Before.class);                return before1.priority() - before2.priority();            }        });        ControllerInstrumentation.stopActionCall();        for (Method before : befores) {            String[] unless = before.getAnnotation(Before.class).unless();            String[] only = before.getAnnotation(Before.class).only();            boolean skip = false;            for (String un : only) {                if (!un.contains(".")) {                    un = before.getDeclaringClass().getName().substring(12).replace("$", "") + "." + un;                }                if (un.equals(request.action)) {                    skip = false;                    break;                } else {                    skip = true;                }            }            for (String un : unless) {                if (!un.contains(".")) {                    un = before.getDeclaringClass().getName().substring(12).replace("$", "") + "." + un;                }                if (un.equals(request.action)) {                    skip = true;                    break;                }            }            if (!skip) {                before.setAccessible(true);                inferResult(invokeControllerMethod(before));            }        }    }

再将handleBeforeValidations处理方法,放到 Play.pluginCollection.beforeActionInvocation前面

ControllerInstrumentation.stopActionCall();        //@BeforeValidation    handleBeforeValidations(request);        Play.pluginCollection.beforeActionInvocation(actionMethod);    // Monitoring    monitor = MonitorFactory.start(request.action + "()");    // 3. Invoke the action    try {        // @Before        handleBefores(request);        // Action        Result actionResult = null;        String cacheKey = null;

这样一来,就可以通过BeforeValidation注解在application中添加相应的切库逻辑。就可以解决Jpa封装对象参数报错问题。 另外还有切库依据,可以将相应的数据源等数据存放一个对象,用sessionid做key存放在redis中,每次请求过来就进行切库,顺便检测是否登陆。

转载于:https://my.oschina.net/markho/blog/498135

你可能感兴趣的文章
如何在Ubuntu命令行下管理浏览器书签
查看>>
《大数据分析原理与实践》一一2.1 大数据分析模型建立方法
查看>>
《 自动化测试最佳实践:来自全球的经典自动化测试案例解析》一一2.7 测试套件和类型...
查看>>
8月18日云栖精选夜读:阿里视频云最强转码技术揭秘:窄带高清原理解析+用户接入指南...
查看>>
涨姿势:工业物联网与大数据融合的四个重点
查看>>
社会学视角下的大数据方法论及其困境
查看>>
《云计算:原理与范式》一1.7 平台即服务供应商
查看>>
百度成立“百度搜索公司”:固本拓新驱动生态裂变
查看>>
宇宙风暴?才怪!瑞典暗指俄罗斯黑客攻击航空控制系统
查看>>
系统进程管理工具Process Explorer
查看>>
富士通仍执着SPARC架构芯片 将坚持推新
查看>>
易宪容:企业要利用大数据挖掘潜在需求
查看>>
微软声称Win10周年更新为Edge浏览器带来更好电池寿命
查看>>
混合云是企业IT的未来吗?
查看>>
LINE在日本取得成功 但全球化之路还很长
查看>>
红帽云套件新增QuickStart Cloud Installer,加快私有云部署
查看>>
MapXtreme 2005 学习心得 一些问题(八)
查看>>
流量精细化运营时代,营销SaaS之使命——流量掘金
查看>>
哥伦比亚大学牙科学院使用RFID系统,更好管理牙科器械
查看>>
雅虎同意出售核心资产
查看>>