文档章节

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

奋斗到天明
 奋斗到天明
发布于 2015/08/27 16:23
字数 1442
阅读 382
收藏 1
点赞 0
评论 0

在单数据源(单个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 ⋅ 0

如何用cmd命令控制mysql数据库

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

大师兄 ⋅ 2014/07/13 ⋅ 1

Go语言的Web框架

我去年开始研究Go语言,不知不觉快有一年了。以前我研究php和nodejs,都是弱类型的解释性语言。想找一个编译型的强类型语言继续学习,就选中了新奇的Go语言。我只关注Web方面的应用,看了很多...

傅小黑 ⋅ 2014/03/13 ⋅ 6

RocksDB 5.9.2 发布,可持久化 key-value 存储系统

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

局长 ⋅ 2017/12/20 ⋅ 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 ⋅ 0

关于Play(Play 2.0 介绍)

Play 2.0 介绍 2007开始,我们一直致力于让Java开发web应用更容易。Play始于一个内部项目Zenexity,它深刻影响了我们开发web项目的方式:关注开发者生产力,遵循web架构的特点,并打破常规,...

大东哥 ⋅ 2012/03/19 ⋅ 0

从Spring + Ejb + Struts2 迁移到Play 1.x 框架 之一

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

刀狂剑痴 ⋅ 2015/08/27 ⋅ 0

Play Framework 2.0 介绍

Play Framework 刚刚发布了 2.0 正式版,来看看 Play Framework 2.0 的新特性介绍吧。 原生支持 Java 和 Scala Play Framework 1.x 完全是采用 Java 编写的,可通过插件来支持 Scala,而 2....

红薯 ⋅ 2012/03/14 ⋅ 20

中英文对照 介绍Play Framework 框架 概述(Overview)

Play框架概述 h1. Play framework overview Play框架是臃肿的企业级Java之外的另一个选择,它关注的是开发的效率和提供REST式的架构风格, Play是“敏捷软件开发”的绝佳伴侣。 The Play fra...

lyuehh ⋅ 2010/07/11 ⋅ 4

From Apprentice To Artisan 翻译 04

上一篇 Reflect Resolution 反射解决方案 One of the most powerful features of the Laravel container is its ability to automatically resolve dependencies via reflection. Reflection......

zgldh ⋅ 2014/10/08 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

JAVA RMI

什么是JAVA RMI Java RMI (Remote Method Invocation) 远程方法调用,能够让客户端像使用本地调用一样调用服务端 Java 虚拟机中的对象方法。RMI 是面向对象语言领域对 RPC (Remote Proced...

saulc ⋅ 8分钟前 ⋅ 0

Linux系统工程狮养成记

如今的社会,随着时代的发展,出现了很多职业,像电子类,计算机类的专业,出现了各种各样的工程师,有算法工程师,java工程师,前端工程师,后台工程师,Linux工程师,运维工程师等等,不同...

linux-tao ⋅ 18分钟前 ⋅ 0

进入编辑模式 vim命令模式 vim实践

1.

oschina130111 ⋅ 18分钟前 ⋅ 0

mysql用户管理、常用sql语句、mysql数据库备份恢复

1. mysql用户管理 mysql默认有一个root超级管理员账户,实际工作环境中不可能每个人都用此root权限,防止误操作、误删除,可以给单独的用户进行授权。 Mysql创建用户以及授权: grant all on...

laoba ⋅ 19分钟前 ⋅ 0

类型后面三个点(String...)和数组(String[])的区别

类型后面三个点(String…),是从Java 5开始,Java语言对方法参数支持一种新写法,叫可变长度参数列表,其语法就是类型后跟…,表示此处接受的参数为0到多个Object类型的对象,或者是一个Obj...

流氓兔- ⋅ 25分钟前 ⋅ 0

JEPLUS表格组件之表格合并——JEPLUS软件快速开发平台

JEPLUS表格组件之表格合并 我们在列表配置时会遇见这样的一种情况,需要对个人的数据进行统一化,对一些数据进行归类,这样展示出来美观又直观,在这篇笔记中我来给大家介绍下如何配置出来专...

JEPLUS ⋅ 26分钟前 ⋅ 0

golang 并发中全局唯一操作

package main// go 携程共享 数据// 加锁解锁操作// 同步锁import ("sync""fmt")// 创建Once结构var once = sync.Once{}func computed(data *int, lock *sync.Mut...

304158 ⋅ 26分钟前 ⋅ 0

Mobx入门之二:asynchronous actions

这一节主要看mobx怎么实现asynchronous actions 1 要实现的demo功能 输入地名,查询天气,利用openweathermap api 2 思想 observable观察数据:location地点、temperature温度 observer响应式...

pengqinmm ⋅ 29分钟前 ⋅ 0

【2018.0620学习笔记】【linux高级知识 13.4-13.6】

13.4 mysql用户管理 13.5 常用sql语句 13.6 mysql数据库备份恢复

lgsxp ⋅ 50分钟前 ⋅ 0

Java强弱引用示例

package jdk;import java.lang.ref.PhantomReference;import java.lang.ref.ReferenceQueue;import java.lang.ref.SoftReference;import java.lang.ref.WeakReference;public ......

月下狼 ⋅ 56分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部