首页 > 代码库 > Mondiran创建连接

Mondiran创建连接

曾经使用jdbc创建连接的时候使用的url是这种形式:jdbc:mysql://hostname:port/database?key1=value1&key2=value2,在URL须要以"jabc:mysql"开头的,当中须要声明数据库server的地址和端口、数据库名另一些其他的属性,以"?"切割,每个属性使用"&"字符切割,可是mondrian作为OLAPserver和mysql这类的关系型数据库还是有所差别的,毕竟它本身不保存不论什么数据。它更像是一个工具类的东西(类似于hive)。可以把MDX翻译成一串SQL语句再交由关系数据库运行,所以它不能独立得称为一个server(在我的理解中,对于server的使用仅仅须要知道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函数。參数为username和password),创建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:这样的情况下会依据是否指定username和password推断是否创建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的password。hive的jdbc连接的url中)则须要将值通过引號括起来作为一个完整的值,全部的參数都保存在RolapConnectionProperties类中。包含
1、Provider:在mondrian中它的值要么不指定要么指定为mondrian
2、Jdbc:指定数据源的jdbc的url信息。
3、JdbcDrivers:指定数据源的driver类,能够指定多个,通过‘,‘切割。
4、JdbcUser:连接数据源的username
5、JdbcPassword:连接数据源的password
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的參数名,值为參数值。

Mondiran创建连接