文档章节

Play 1.x框架学习之七:多数据库切换与源码修改 ( Databases Switch And Modify Source Code)

奋斗到天明
 奋斗到天明
发布于 2015/08/27 16:23
字数 1442
阅读 493
收藏 1

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

jpa.dialect=org.hibernate.dialect.MySQLDialect
db.url=jdbc:mysql://basedbip:3306/pop?autoReconnect=true&useUnicode=true&characterEncoding=utf-8
db.driver=com.mysql.jdbc.Driver
db.user=root
db.pass=root
db=pop

db_01.url=jdbc:mysql://anotherdbip:3306/pop?autoReconnect=true&useUnicode=true&characterEncoding=utf-8
db_01.driver=com.mysql.jdbc.Driver
db_01.user=root
db_01.pass=root
db_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 more
Caused 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 more
Caused 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 more
Caused 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.java
package 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中,每次请求过来就进行切库,顺便检测是否登陆。

© 著作权归作者所有

共有 人打赏支持
奋斗到天明
粉丝 18
博文 112
码字总数 82707
作品 0
昌平
程序员
用Play 1.x 实现简单云计算多租户设计(Use Play 1.x To Achieve Multi-Tenancy Design)

这里的云计算多租户是指一个web应用,多个数据库。每一个租户对应着一个数据库。 数据库方面,简单分为一个基本库,记录着基本信息与租户的信息,还有租户数据库配置信息。N个租户库,这N个租...

刀狂剑痴
2015/08/27
104
0
如何用cmd命令控制mysql数据库

CMD命令大全 SHOW VARIABLES LIKE 'character%'; //显示自己的数据库的编码 chcp 65001 就是换成UTF-8代码页 chcp 936 可以换回默认的GBK chcp 437 是美国英语 set names gbk //设置数据库的...

大师兄
2014/07/13
0
1
RocksDB 5.9.2 发布,可持久化 key-value 存储系统

RocksDB 5.9.2 已发布,该版本的更新包括 Public API 的修改、新增特性和修复 Bug。 RocksDB 是一个来自 Facebook 的可嵌入的支持持久化的 key-value 存储系统,也可作为 C/S 模式下的存储数...

局长
2017/12/20
374
0
Play 1.x 日期格式与参数绑定 (Play 1.x Date Format And Parameter Binding)

昨天项目中遇到一个棘手的问题。是关于日期格式的。 项目是前端Delphi,后端Play 1.x。在进行数据交互的时候。日期有两种格式,长格式:yyyy-MM-dd HH:mm:ss,短格式:yyyy-MM-dd。 在Play ...

刀狂剑痴
2015/08/27
34
0
从Spring + Ejb + Struts2 迁移到Play 1.x 框架 之一

原来项目比较古老,前台是用delphi,后台有用Ejb做……这货已经很少有人见过了……,现在公司主要项目都转到play上,所以这个项目也重构。 第一阶段是将SSE 迁移到play,尽量不改动代码,只要...

刀狂剑痴
2015/08/27
129
0

没有更多内容

加载失败,请刷新页面

加载更多

django 2 urlpatterns 中正则匹配路由

django 2 urlpatterns 中正则匹配路由: 在项目的urls.py中导入re_path:

MichaelShu
19分钟前
0
0
Spring MVC 到 Spring Boot 的简化之路

背景 从Servlet技术到Spring和Spring MVC,开发Web应用变得越来越简捷。但是Spring和Spring MVC的众多配置有时却让人望而却步,相信有过Spring MVC开发经验的朋友能深刻体会到这一痛苦。因为...

别打我会飞
25分钟前
0
0
python做文本内容指定区域字符串替换

需求: 因为公司项目需要做SEO优化,所以对项目中的各种长连接做优化,比如本文中提到的精简路径;之前已经批量吧文本的路径名字等做过修改,这里不再赘述;这里的问题是外部的路径修改了,文...

坦途abc
50分钟前
4
0
MySQL 关键字模糊匹配,并按照匹配度排序

MySQL 关键字模糊匹配,并按照匹配度排序。 方式一、按照关键字搜索,然后根据关键字所占比例排序 SELECTdrug_name,pinyinFROMtbl_drugWHEREpinyin LIKE '%AM%'ORDER BY...

yh32
今天
3
0
虚拟机学习之一:java内存区域与内存溢出异常

1.运行时数据区域 java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途和创建、销毁时间,有的区域伴随虚拟机进程的启动而存在,有些区...

贾峰uk
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部