欢迎投稿

今日深度:

Mondiran创建连接,mondiran创建

Mondiran创建连接,mondiran创建


以前使用jdbc创建连接的时候使用的url是这样的形式:jdbc:mysql://hostname:port/database?key1=value1&key2=value2,在URL需要以"jabc:mysql"开头的,其中需要声明数据库服务器的地址和端口、数据库名还有一些其它的属性,以"?"分割,每一个属性使用"&"字符分割,但是mondrian作为OLAP服务器和mysql这类的关系型数据库还是有所区别的,毕竟它本身不保存任何数据,它更像是一个工具类的东西(类似于hive),能够把MDX翻译成一串SQL语句再交由关系数据库执行,所以它不能独立得称为一个服务器(在我的理解中,对于服务器的使用只需要知道IP和port以及一些其他的参数就足够了)。      为了能够使用java自带的DriverManager创建connection,mondrian做了这个兼容,在使用mysql这样的数据库的时候我们会首先执行如下的操作:
Class. forName(driver);
connection = DriverManager. getConnection(url , username ,password );

getConnection函数返回的是java.sql.Connection对象,这个Connection是通用的数据库连接,但是mondrian也是用了相同的方式创建到OLAP引擎的连接,它的driver为mondrian.olap4j.MondrianOlap4jDriver,它的url有自己独特的结构,下面就看一下在mondrian中是如何创建连接的,除了之上的两部,还需要再进行其他的操作:
Class. forName(driver);
connection = DriverManager. getConnection(url , username ,password );
OlapConnection olapConnection = connection.unwrap(OlapConnection.class);

     在mondrian中的url格式如下:jdbc:mondrian:Jdbc=jdbc:mysql://10.241.20.157:3306/foodmart?user=root&password=root;Catalog=C:\\Users\\Administrator\\Desktop\\nrtp\\FoodMart.xml;可以看出url中的每一项是通过";"分割的,类似于mysql的开头是"jdbc:mysql",mondrian连接的url的开头必须是"jdbc:mondrian:",另外还包括"Jdbc"和"Catalog"属性字段,在DriverManager的getConnection静态方法中执行如下操作:
    public static Connection getConnection(String url,
        String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();
        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }
        return (getConnection(url, info, Reflection.getCallerClass()));
    }

这个函数是所有的数据库连接创建的方式,其实就是创建一个Properties对象,然后加上user和password属性,然后调用其它的getConnection方法:
    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized (DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;

        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }
            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }
        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }

        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }

     整了这么多其实就是那个for语句执行真正的创建连接,前面只是设置classloader,因为在调用getConnection之前我们使用Class.forName函数只给了一个driver的类名,这里势必要使用反射创建一个对象,但是在这个for循环中遍历了registeredDrivers对象,这是DriverManager类的一个static的List对象,但是我们在调用getConnection之前并没有对DriverManager类做任何操作,这个对象是什么时候放入数据的呢,这时候就要看一下在执行getConnection之前的Class.forName方法了,这个方法其实就是使用当前的classLoader将指定的类加载进来,加载类的时候会初始化这个类(部署初始化任何对象),包括初始化一些static代码区,果然,在mondrian.olap4j.MondrianOlap4jDriver类中有这样一段static代码区:
    static {
        try {
            register();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

     这里register方法执行了这样一个语句:DriverManager.registerDriver(new MondrianOlap4jDriver());哈哈,正是在这里将这个driver注册到DriverManager的,在registerDriver函数中,DriverManager将注册的driver对象加入到registeredDrivers对象中的,这样也就解开了之前的疑惑,一直在疑惑这一句forName有什么作用。我想其它的driver类(包括mysql、oracle等)都应该使用这种方式吧。      接续看getConnection方法,它遍历registeredDrivers列表中的每一个成员,然后尝试用每一个driver创建connection,直到遇到第一个能够创建成功的driver,如果都创建失败,reason中的错误信息是第一个driver创建connection的失败信息,真正创建connection的方法是Connection con = aDriver.driver.connect(url, info);,其中info是包含了user和password信息的properties,接下来就再次回到MondrianOlap4jDriver类中执行创建连接了,从这里可以看出DriverManager其实就是一个工厂,需要创建connection的时候向它注册一个driver,创建对象的时候使用每一个具体的driver完成。MondrianOlap4jDriver类中创建connection的方法如下:
    public Connection connect(String url, Properties info) throws SQLException {
        if (!MondrianOlap4jConnection.acceptsURL(url)) {
            return null;
        }
        return factory.newConnection(this, url, info);
    }

     这里的factory是一个driver的一个成员变量,它是在driver初始化的时候创建的,在mondrian中它是根据不同的jdbc版本使用不同的factory,具体的策略是不同的版本的jdbc中包含不同的class,然后通过Class.forName判断该class是否存在已决定当前的版本(这个策略可以省去一些版本配置的信息),当前使用的factory是"mondrian.olap4j.FactoryJdbc41Impl",这个函数会创建并返回一个MondrianOlap4jConnectionJdbc41对象,这个对象实际创建的是他的父类对象MondrianOlap4jConnection,它的构造函数如下:     MondrianOlap4jConnection( Factory factory, MondrianOlap4jDriver driver,String url,Properties info) throws SQLException 参数分别是上面提到的factory对象,driver对象。连接url和DriverManager传递过来的配置信息。我觉得这个对象其实就是为了实现olap4j接口在olap4j接口和mondrian真正的connection之间做的一层转换,创建connection主要是创建一个mondrian原生的connection,然后保存和这个connection相关的server、schema等信息,创建connection的语句如下:
        this.mondrianConnection = (RolapConnection) mondrian.olap.DriverManager .getConnection(list, null);

     创建mondrian连接的是通过mondrian的DriverManager实现的,其中list为解析之后的url中的key-value对和info中的配置信息,第二个参数是CatalogLocator对象,这里为null,实际上并没有什么卵用。      创建mondrian连接的第一步就是创建指定的server,在mondrian中server是用来维护和管理connection的,每次连接创建的时候会根据URL中指定的"Instance"配置决定使用哪个server(为什么做成多个server后期再研究),如果没有指定这个配置则使用默认的server。在mondrian中所有的server是由MondrianServerRegistry这个全局对象维护的,但是在根据Instance查找server的时候如果找不到则会抛出异常,而不会创建新的server,通过跟踪调用逻辑发现只有在使用XMLA服务的时候才会创建server,这里暂不讨论,假设所有的URL中都不包含Instance配置。创建server的入口为MondrianServerRegistry.createWithRepository,它需要两个参数,分别为RepositoryContentFinder对象和CatalogLocator对象,默认的server也都是用默认的这两个对象,其中CatalogLocator对象用来处理URL中指定的Catalog信息,默认不做任何处理      接着创建一个RolapConnection对象,这个对象就是mondrian内部的连接,它的构造函数为:RolapConnection(MondrianServer server,Util.PropertyList connectInfo,RolapSchema schema,DataSource dataSource),参数分别为指定的server、URL的配置信息,使用的Schema对象,这里为null,指定的datasource信息,这里为null。      每一个连接会被赋予一个全局唯一的ID,它是递增的,然后判断Provider配置,这个配置要么在URL中指定为mondrian要么不指定。接着分别提取"Catalog"、“JdbcUser”、"Jdbc"和"DataSource"配置的值,然后根据URL创建一个底层数据员的dataSource对象(DataSource是一个接口,实现了定义了getConnection函数,参数为用户名和密码),创建dataSource对象是根据URL中指定的Jdbc、JdbcUser和JdbcPassword配置完成的,另外这时还会获取URL中指定的JdbcDrivers配置加载这里指定的所有的driver(每一个driver使用,分割),接着再加载所有默认的drivers,包括"mondrian.jdbcDrivers", "sun.jdbc.odbc.JdbcOdbcDriver,org.hsqldb.jdbcDriver,oracle.jdbc.OracleDriver,com.mysql.jdbc.Driver"(load之前会判断是否已经加载),接着再从URL中查找所有数据源jdbc连接的参数。这些参数是通过jdbc.xxx=yyy指定的,其中xxx为参数名,yyy为参数值。接着再根据URL中指定的"PoolNeeded"参数已决定是否创建一个datasource池,默认情况下是对于使用指定Jdbc创建的数据源会创建这个池子,而对于通过DataSource参数指定的数据源则不创建。这时候创建的DataSource对象分成了四种情况(当Jdbc和DataSource同时指定的时候使用Jdbc): 1、在mondrian连接的URL中指定了Jdbc并且没有指定PoolNeeded:这种情况下会RolapConnectionPool中根据jdbc的url和jdbc的配置信息从池子中获取DataSource对象(如果是mysql会加上autoReconnect=true),实际返回的是dbcp中提供的PoolingDataSource类型对象。 2、在mondrian连接的URL中指定了Jdbc同时指定PoolNeeded=false:这种情况下mondrian会认为你不需要进行缓存,因此会创建一个新的DataSource对象,类型为DriverManagerDataSource,每次getConnection的时候都是使用java提供的DriverManager老老实实地创建一个connection。 3、在mondrian连接的URL中指定了DataSource并没有指定PoolNeeded:这种情况下同样也会使用RolapConnectionPool创建一个缓存的DataSource对象。 4、在mondrian连接的URL中指定了DataSource并指定PoolNeeded=true:这种情况下会根据是否指定用户名和密码判断是否创建UserPasswordDataSource对象(这玩意其实是一个代理),如果都没指定则根据DataSource参数指定的类使用该类的默认构造函数创建一个dataSource对象。      上面不管是使用哪种方式创建DataSource都将会被保存在mondrian的connection内部成员,每次创建向数据源的连接时都是用getConnection获取。      创建完DataSource会将当前的mondrian的connection加入到server中,由一个map保存,其中key为connection分配的id,value为connection对象。      接着是根据schema参数是否为null以执行不同的操作,当前schema等于null,则会创建一个statement,并加入到Locus(这个东东在mondrian中经常用到,后面再详述)中,接着创建一个Schema对象,这个对象是这里的重点,解析并保存了xml文件中定义的所有cube的信息,等下再看,先看下创建完schema之后会解析URL中的"Role"参数,并处理当前connection的role,默认情况下使用schema的默认role(ALL)。      至此mondrian的connection就创建完成了,主要的操作就是创建DataSource对象和Schema对象,后者的创建全是通过RolapSchemaPool提高的get接口完成,在创建的时候会根据"UseSchemaPool"参数以决定是否使用schema池,默认为true,如果指定了false则创建一个新的Schema对象,否则需要根据"JdbcConnectionUuid"参数、jdbc连接的url信息、dataSource参数以及读取的catalog全部内容的md5值作为key从schemaPool中查找。除此之外,如果在URL中指定了"UseContentChecksum"参数,并根据该参数判断是否只根据catalog的全部内容的md5值作为key查找(也就是不必使用jdbc的url作为key的一部分了),当然无论使用哪种方式从池中获取schema,schema加入池子的时候都有一个过期时间,由"UseSchemaPool"参数指定,如果不指定则设为-1s(具体含义后面再讲)。      创建schema的过程是比较复杂的,主要是要加载的schema文件的全部内容并做一些初始化操作,本文不作讲述,除此之外在创建schema的时候还会创建一个mondrian连接(其实并没有什么用),这里递归的执行RolapConnection的构造函数(见上面),但是这时候schema已经不为null了,创建的时候会创建完DataSource之后创建一个connection,然后就返回了,并且schema中创建的这个InternalConnection并不会执行任何其他的操作,因此可以认为这一步创建connection只是为了测试数据源的连通性。此外Schema构造函数还会创建AggTableManager对象和DataSourceChangeListener对象      创建完schema之后再从schema文件中(catalog内容)加载所有的cube以及相关信息,这一部相当复杂。在创建完mondrian连接之后还需要对olap4j连接进行一些额外的设置,以后用到的时候再讲。      最后对所有创建connection的URL中出现的参数进行一个总结,参数之间通过';'进行分割,如果在某一个参数的值中需要出现';'(例如jdbc的密码,hive的jdbc连接的url中)则需要将值通过引号括起来作为一个完整的值,所有的参数都保存在RolapConnectionProperties类中,包括 1、Provider:在mondrian中它的值要么不指定要么指定为mondrian 2、Jdbc:指定数据源的jdbc的url信息。 3、JdbcDrivers:指定数据源的driver类,可以指定多个,通过','分割。 4、JdbcUser:连接数据源的用户名 5、JdbcPassword:连接数据源的密码 6、Catalog:schema的地址,可以是HTTP的URL或本地文件等。 7、CatalogContent:schema的全部内容,一个xml文件。 8、CatalogName:schema的名字,当前未使用。 9、DataSource:指定的DataSource类型,必须实现javax.sql.DataSource接口,在URL中必须指定Jdbc或者DataSource 10、PoolNeeded:是否缓存DataSource对象。 11、Role:当前连接使用的权限信息。 12、UseContentChecksum:是否只是用schema文件的内容作为key查找schema。 13、UseSchemaPool:是否使用schema池,如果指定则根据前一项判断根据什么来作为查找schema的key,如果为false则每次都创建一个新的schema。 14、DynamicSchemaProcessor:可以指定一个类动态的修改schema内容。 15、Locale:设置的本地化信息,默认使用系统的locale 16、DataSourceChangeListener:可以设置一个实现mondrian.spi.DataSourceChangeListener接口的类,用于判断数据源是否发生改变,如果发生改变则需要更新缓存。 17、Ignore:配置是否忽略加载schema时的非致命错误和警告。 18、Instance:指定创建connection所在的server,不指定则使用默认的server 19、JdbcConnectionUuid:标识jdbc连接的id,如果指定则可以通过该配置判断两个jdbc的连接是否相同。 20、PinSchemaTimeout:缓存schema的过期时间,如果指定为正数则表示缓存schema强引用的过期时间,如果过期则会被垃圾回收,如果指定为0则表示永不过期,如果指定为负数则表示将schema保存为一个软引用,它的绝对值为该软引用的过期时间,这里的时间单元可以设置为d, h, m, s, ms。 21、jdbc.:jdbc参数信息,后面指定jdbc的参数名,值为参数值。

版权声明:本文为博主原创文章,未经博主允许不得转载。

www.htsjk.Com true http://www.htsjk.com/shujukunews/9735.html NewsArticle Mondiran创建连接,mondiran创建 以前使用jdbc创建连接的时候使用的url是这样的形式: jdbc:mysql://hostname:port/database?key1=value1key2=value2,在URL需要以jabc:mysql开头的,其中需要声明数据库服务器的...
评论暂时关闭