/*
* JEF - Copyright 2009-2010 Jiyi (mr.jiyi@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jef.database;
import java.io.File;
import java.sql.SQLException;
import java.util.Map;
import javax.sql.DataSource;
import jef.codegen.EntityEnhancer;
import jef.common.log.LogUtil;
import jef.database.datasource.MapDataSourceLookup;
import jef.database.datasource.RoutingDataSource;
import jef.database.dialect.AbstractDialect;
import jef.database.dialect.DatabaseDialect;
import jef.database.jpa.JefEntityManagerFactory;
import jef.database.meta.MetaHolder;
import jef.database.support.QuerableEntityScanner;
import jef.tools.JefConfiguration;
import org.apache.commons.lang.StringUtils;
import org.easyframe.enterprise.spring.TransactionMode;
/**
* 提供了创建DbClient的若干工厂方法
*
*
*/
public class DbClientBuilder {
// 为了防止在单元测试中反复执行文件扫描。
private static long lastEnhanceTime;
/**
* 多数据源。分库分表时可以使用。 在Spring配置时,可以使用这样的格式来配置
*
* <pre>
* <code>
* <property name="dataSources">
* <map>
* <entry key="dsname1" value-ref="ds1" />
* <entry key="dsname2" value-ref="ds2" />
* </map>
* </property>
* </code>
* </pre>
*/
private Map<String, DataSource> dataSources;
/**
* 单数据源。
*/
protected DataSource dataSource;
/**
* 多数据源时的缺省数据源名称
*/
private String defaultDatasource;
/**
* 内置连接池最大连接数
*/
private int maxPoolSize = JefConfiguration.getInt(DbCfg.DB_CONNECTION_POOL_MAX, 50);
/**
* 内置连接池最小连接数
*/
private int minPoolSize = JefConfiguration.getInt(DbCfg.DB_CONNECTION_POOL, 3);
/**
* 命名查询所在的文件
*/
private String namedQueryFile;
/**
* 命名查询所在的表
*/
private String namedQueryTable;
/**
* 事务支持类型
*
* @see #setTransactionMode(String)
*/
private TransactionMode transactionMode;
/**
* 指定对以下包内的实体做一次增强扫描。多个包名之间逗号分隔。<br>
* 如果不配置此项,默认将对packagesToScan包下的类进行增强。<br>
* 不过不想进行类增强扫描,可配置为"none"。
*/
private String enhancePackages;
/**
* 指定扫描若干包,配置示例如下—— <code><pre>
* <list>
* <value>org.easyframe.test</value>
* <value>org.easyframe.entity</value>
* </list>
* </pre></code>
*/
private String[] packagesToScan;
/**
* 扫描已知的若干注解实体类,配置示例如下—— <code><pre>
* <list>
* <value>org.easyframe.testp.jta.Product</value>
* <value>org.easyframe.testp.jta.Users</value>
* </list>
* </pre></code>
*/
private String[] annotatedClasses;
/**
* 指定扫描若干表作为动态表,此处配置表名,名称之间逗号分隔
*/
private String dynamicTables;
/**
* 是否将所有未并映射的表都当做动态表进行映射。
*/
private boolean registeNonMappingTableAsDynamic;
/**
* 扫描到实体后,如果数据库中不存在,是否建表 <br>
* 默认开启
*/
private boolean createTable = true;
/**
* 扫描到实体后,如果数据库中存在,是否修改表 <br>
* 默认开启
*/
private boolean alterTable = true;
/**
* 扫描到实体后,如果准备修改表,如果数据库中的列更多,是否允许删除列 <br>
* 默认关闭
*/
private boolean allowDropColumn;
/**
* 在建表后插入初始化数据<p>
* EF-ORM允许用户在和class相同的位置创建一个 <i>class-name</i>.init.json的文件,记录了表中的初始化数据。
* 开启此选项后,在初始化建表时会插入这些数据。
*/
private boolean initDataAfterCreate;
/**
* 如果表已经存在,检查初始化的必备数据是否已经存在于表中,如无则插入
* EF-ORM允许用户在和class相同的位置创建一个 <i>class-name</i>.init.json的文件,记录了表中的初始化数据。
* 开启此选项后,在启动扫描表后会检查表中是否存在这些数据,如不存在或不一致会修改这些数据。
*/
private boolean initDataIfTableExists;
/**
* 最终构造出来的对象实例
*/
protected JefEntityManagerFactory instance;
/**
* 空构造
*/
public DbClientBuilder() {
}
/**
* 构造
*
* @param 是否对classpath下的目录进行类增强
*/
public DbClientBuilder(boolean enhance) {
if (!enhance) {
this.setEnhancePackages("none");
}
}
/**
* 构造
*
* @param jdbcURL
* @param user
* @param password
* @param maxPool
*/
public DbClientBuilder(String jdbcURL, String user, String password, int maxPool) {
this.dataSource = DbUtils.createSimpleDataSource(jdbcURL, user, password);
this.maxPoolSize = maxPool;
}
/**
* 构造数据源连接信息
*
* @param dbType
* @param host
* @param port
* @param pathOrName
* @param user
* @param password
* @return
*/
public DbClientBuilder(String dbType, String host, int port, String pathOrName, String user, String password) {
DatabaseDialect profile = AbstractDialect.getProfile(dbType);
if (profile == null) {
throw new IllegalArgumentException("The DBMS:[" + dbType + "] is not supported yet.");
}
String dbURL = profile.generateUrl(host, port, pathOrName);
this.dataSource = DbUtils.createSimpleDataSource(dbURL, user, password);
}
/**
* 工厂方法获得数据库实例(本地)
*
* @param dbName
* 数据库名
* @param user
* 用户
* @param pass
* 密码
* @return
* @throws SQLException
*/
public DbClientBuilder(String dbType, File dbFolder, String user, String password) {
int port = JefConfiguration.getInt(DbCfg.DB_PORT, 0);
String host = JefConfiguration.get(DbCfg.DB_HOST, "");
DatabaseDialect profile = AbstractDialect.getProfile(dbType);
if (profile == null) {
throw new IllegalArgumentException("The DBMS:[" + dbType + "] is not supported yet.");
}
String dbURL = profile.generateUrl(host, port, dbFolder.getAbsolutePath());
this.dataSource = DbUtils.createSimpleDataSource(dbURL, user, password);
}
/**
* 根据JDBC连接字符串和用户名密码得到
*
* @param jdbcUrl
* @param user
* @param password
* @throws SQLException
*/
public DbClientBuilder(String jdbcUrl, String user, String password) {
this.setDataSource(DbUtils.createSimpleDataSource(jdbcUrl, user, password));
}
/**
* 获得构造完成的DbClient对象
*
* @return DbClient
* @see DbClient
*/
public DbClient build() {
if (instance == null) {
instance = buildSessionFactory();
}
return instance.getDefault();
}
/**
* 获得当前的事务控制模式
*
* @return 事务控制模式
* @see TransactionMode
*/
public String getTransactionMode() {
return transactionMode == null ? null : transactionMode.name();
}
/**
* 事务管理模式,可配置为
* <ul>
* <li><strong>JPA</strong></li><br>
* 使用JPA的方式管理事务,对应Spring的
* {@linkplain org.springframework.orm.jpa.JpaTransactionManager
* JpaTransactionManager}, 适用于ef-orm单独作为数据访问层时使用。
* <li><strong>JTA</strong></li><br>
* 使用JTA的分布式事务管理。使用JTA可以在多个数据源、内存数据库、JMS目标之间保持事务一致性。<br>
* 推荐使用atomikos作为JTA管理器。 对应Spring的
* {@linkplain org.springframework.transaction.jta.JtaTransactionManager
* JtaTransactionManager}。<br>
* 当需要在多个数据库之间保持事务一致性时酌情使用。
* <li><strong>JDBC</strong></li><br>
* 使用JDBC事务管理。当和Hibernate一起使用时,可以利用Hibernate的连接共享Hibernate事务。
* 当与JdbcTemplate共同使用时, 也可以获得DataSource所绑定的连接从而共享JDBC事务。 对应Spring的
* {@linkplain org.springframework.orm.hibernate3.HibernateTransactionManager
* HibernateTransactionManager} 和
* {@linkplain org.springframework.jdbc.datasource.DataSourceTransactionManager
* DataSourceTransactionManager}。
* 一般用于和Hibernate/Ibatis/MyBatis/JdbcTemplate等共享同一个事务。
* </ul>
* 默认为{@code JPA}
*
* @param txType
* 事务管理模式,可设置为JPA、JTA、JDBC
*
* @see TransactionMode
*/
public DbClientBuilder setTransactionMode(String txType) {
this.transactionMode = TransactionMode.valueOf(StringUtils.upperCase(txType));
return this;
}
/**
* 设置内置连接池最大连接数,如果设置为0可以禁用内置连接池。
*
* @param maxConnection
* @return
*/
public DbClientBuilder setMaxPoolSize(int maxConnection) {
this.maxPoolSize = maxConnection;
return this;
}
public DataSource getDataSource() {
return dataSource;
}
/**
* 设置数据源
*
* @param dataSource
* 数据源
*/
public DbClientBuilder setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
return this;
}
/**
* 设置要扫描的包
*
* @return 要扫描的包,逗号分隔
*/
public DbClientBuilder setPackagesToScan(String[] scanPackages) {
this.packagesToScan = scanPackages;
return this;
}
/**
*
* @return
*/
public boolean isAlterTable() {
return alterTable;
}
/**
* 扫描到实体后,是否修改数据库中与实体定义不同的表
*
* @param alterTable
* 'true' , EF-ORM will alter tables in database.
*/
public DbClientBuilder setAlterTable(boolean alterTable) {
this.alterTable = alterTable;
return this;
}
/**
* 设置是否处于调试
* @param debug
* @return
*/
public DbClientBuilder setDebug(boolean debug) {
ORMConfig.getInstance().setDebugMode(debug);
return this;
}
/**
* 查询是否为调试模式
* @return
*/
public boolean isDebug(){
return ORMConfig.getInstance().isDebugMode();
}
/**
* 查询是否会自动建表
* @return
*/
public boolean isCreateTable() {
return createTable;
}
/**
* 扫描到实体后,是否在数据库中创建不存在的表
*
* @param createTable
* true将会创建表
*/
public DbClientBuilder setCreateTable(boolean createTable) {
this.createTable = createTable;
return this;
}
public boolean isAllowDropColumn() {
return allowDropColumn;
}
/**
* 扫描数据库中存在的表作为动态表模型
*
* @param dynamicTables
* 表名,逗号分隔
*/
public DbClientBuilder setDynamicTables(String dynamicTables) {
this.dynamicTables = dynamicTables;
return this;
}
public boolean isRegisteNonMappingTableAsDynamic() {
return registeNonMappingTableAsDynamic;
}
public String[] getAnnotatedClasses() {
return annotatedClasses;
}
/**
* 扫描已知的若干注解实体类,配置示例如下—— <pre><code>
* <list>
* <value>org.easyframe.testp.jta.Product</value>
* <value>org.easyframe.testp.jta.Users</value>
* </list>
* </code></pre>
*/
public DbClientBuilder setAnnotatedClasses(String[] annotatedClasses) {
this.annotatedClasses = annotatedClasses;
return this;
}
public String[] getPackagesToScan() {
return packagesToScan;
}
public boolean isInitDataAfterCreate() {
return initDataAfterCreate;
}
public void setInitDataAfterCreate(boolean initDataAfterCreate) {
this.initDataAfterCreate = initDataAfterCreate;
}
public void setMinPoolSize(int minPoolSize) {
this.minPoolSize = minPoolSize;
}
/**
* 扫描数据库中当前schema下的所有表,如果尚未有实体与该表对应,那么就将该表作为动态表建模。
*
* @param registeNonMappingTableAsDynamic
*/
public DbClientBuilder setRegisteNonMappingTableAsDynamic(boolean registeNonMappingTableAsDynamic) {
this.registeNonMappingTableAsDynamic = registeNonMappingTableAsDynamic;
return this;
}
public Map<String, DataSource> getDataSources() {
return dataSources;
}
public String getDefaultDatasource() {
return defaultDatasource;
}
/**
* 设置多数据源时的缺省数据源名称
*
* @param defaultDatasource
* name of the datasource.
*/
public DbClientBuilder setDefaultDatasource(String defaultDatasource) {
this.defaultDatasource = defaultDatasource;
return this;
}
/**
* 多数据源。分库分表时可以使用。 在Spring配置时,可以使用这样的格式来配置
*
* <pre>
* <code>
* <property name="dataSources">
* <map>
* <entry key="dsname1" value-ref="ds1" />
* <entry key="dsname2" value-ref="ds2" />
* </map>
* </property>
* </code>
* </pre>
*/
public DbClientBuilder setDataSources(Map<String, DataSource> datasources) {
this.dataSources = datasources;
return this;
}
/**
* 设置存放命名查询的文件资源名(xml格式,将在classpath下查找)
*
* @param namedQueryFile
* 命名查询文件名
*/
public DbClientBuilder setNamedQueryFile(String namedQueryFile) {
this.namedQueryFile = namedQueryFile;
return this;
}
public String getNamedQueryTable() {
return namedQueryTable;
}
/**
* 设置存放命名查询的数据库表名
*
* @param namedQueryTable
* 命名查询数据库表
*/
public DbClientBuilder setNamedQueryTable(String namedQueryTable) {
this.namedQueryTable = namedQueryTable;
return this;
}
/**
* 扫描到实体后,在Alter数据表时,是否允许删除列。
*
* @param allowDropColumn
* true允许删除列
*/
public DbClientBuilder setAllowDropColumn(boolean allowDropColumn) {
this.allowDropColumn = allowDropColumn;
return this;
}
public String getEnhancePackages() {
return enhancePackages;
}
/**
* 是否检查并增强实体。 注意,增强实体仅对目录中的class文件生效,对jar包中的class无效。
*
* @param enhancePackages
* 要扫描的包
*/
public DbClientBuilder setEnhancePackages(String enhancePackages) {
this.enhancePackages = enhancePackages;
return this;
}
public String getDynamicTables() {
return dynamicTables;
}
public String getNamedQueryFile() {
return namedQueryFile;
}
protected JefEntityManagerFactory buildSessionFactory() {
if (instance != null)
return instance;
// try enahcen entity if theres 'enhancePackages'.
if (enhancePackages != null) {
if (!enhancePackages.equalsIgnoreCase("none")) {
new EntityEnhancer().enhance(StringUtils.split(enhancePackages, ","));
}
} else if (packagesToScan != null) {
// if there is no enhances packages, try enhance 'package to Scan'
new EntityEnhancer().enhance(packagesToScan);
} else {
if (System.currentTimeMillis() - lastEnhanceTime > 30000) {
new EntityEnhancer().setExcludePatter(new String[] { "java", "javax", "org.apache", "org.eclipse", "junit", "ant","org.codehaus" }).enhance();
}
lastEnhanceTime = System.currentTimeMillis();
}
JefEntityManagerFactory sf;
// check data sources.
if (dataSource == null && dataSources == null) {
LogUtil.info("No datasource found. Using default datasource in jef.properties.");
sf = new JefEntityManagerFactory(null, minPoolSize, maxPoolSize, transactionMode);
} else if (dataSource != null) {
sf = new JefEntityManagerFactory(dataSource, minPoolSize, maxPoolSize, transactionMode);
} else {
RoutingDataSource rs = new RoutingDataSource(new MapDataSourceLookup(dataSources));
sf = new JefEntityManagerFactory(rs, minPoolSize, maxPoolSize, transactionMode);
}
if (namedQueryFile != null) {
sf.getDefault().setNamedQueryFilename(namedQueryFile);
}
if (namedQueryTable != null) {
sf.getDefault().setNamedQueryTablename(namedQueryTable);
}
if (packagesToScan != null || annotatedClasses != null) {
QuerableEntityScanner qe = new QuerableEntityScanner();
if (transactionMode == TransactionMode.JTA) {
// JTA事务下,DDL语句必须在已启动后立刻就做,迟了就被套进JTA是事务中,出错。
qe.setCheckSequence(false);
}
qe.setImplClasses(DataObject.class);
qe.setAllowDropColumn(allowDropColumn);
qe.setAlterTable(alterTable);
qe.setCreateTable(createTable);
qe.setInitDataAfterCreate(this.initDataAfterCreate);
qe.setInitDataIfTableExists(this.initDataIfTableExists);
qe.setEntityManagerFactory(sf);
if(annotatedClasses!=null)
qe.registeEntity(annotatedClasses);
if (packagesToScan != null) {
String joined = StringUtils.join(packagesToScan, ',');
qe.setPackageNames(joined);
LogUtil.info("Starting scan easyframe entity from package: " + joined);
qe.doScan();
}
}
if (dynamicTables != null) {
DbClient client = sf.getDefault();
for (String s : StringUtils.split(dynamicTables, ",")) {
String table = s.trim();
registe(client, table);
}
}
if (registeNonMappingTableAsDynamic) {
DbClient client = sf.getDefault();
try {
for (String tableName : client.getMetaData(null).getTableNames()) {
if (MetaHolder.lookup(null, tableName) != null) {
registe(client, tableName);
}
}
} catch (SQLException e) {
LogUtil.exception(e);
}
}
return sf;
}
/**
*
* @return
*/
public boolean isInitDataIfTableExists() {
return initDataIfTableExists;
}
/**
* 如果表已经存在,检查初始化的必备数据是否已经存在于表中,如无则插入
* EF-ORM允许用户在和class相同的位置创建一个 <i>class-name</i>.init.json的文件,记录了表中的初始化数据。
* 开启此选项后,在启动扫描表后会检查表中是否存在这些数据,如不存在或不一致会修改这些数据。
* <p>
* 一般来说只有在开发环境中才需要开启此开关
* @param initDataIfTableExists true开启
*
*/
public void setInitDataIfTableExists(boolean initDataIfTableExists) {
this.initDataIfTableExists = initDataIfTableExists;
}
private void registe(DbClient client, String table) {
if (MetaHolder.getDynamicMeta(table) == null) {
try {
MetaHolder.initMetadata(client, table);
LogUtil.show("DynamicEntity: [" + table + "] registed.");
} catch (SQLException e) {
LogUtil.exception(e);
}
}
}
public boolean isCacheDebug() {
return ORMConfig.getInstance().isCacheDebug();
}
public DbClientBuilder setCacheDebug(boolean cacheDebug) {
ORMConfig.getInstance().setCacheDebug(cacheDebug);
return this;
}
public boolean isCacheLevel1() {
return ORMConfig.getInstance().isCacheLevel1();
}
/**
*
* @param cache
* @return this
* @see DbCfg#CACHE_LEVEL_1
*/
public DbClientBuilder setCacheLevel1(boolean cache) {
ORMConfig.getInstance().setCacheLevel1(cache);
return this;
}
/**
* 获得全局缓存的生存时间,单位秒。
* 全局缓存是类似于一级缓存的一个存储结构,可以自动分析数据库操作关联性并进行数据刷新。
* @return 秒数
* @see DbCfg#CACHE_GLOBAL_EXPIRE_TIME
*/
public int getGlobalCacheLiveTime() {
return ORMConfig.getInstance().getCacheLevel2();
}
/**
* 设置全局缓存的生存时间,单位秒。
* 全局缓存是类似于一级缓存的一个存储结构,可以自动分析数据库操作关联性并进行数据刷新。
* @param second
* @return this
* @see DbCfg#CACHE_GLOBAL_EXPIRE_TIME
*/
public DbClientBuilder setGlobalCacheLiveTime(int second) {
ORMConfig.getInstance().setCacheLevel2(second);
return this;
}
public DbClientBuilder setUseSystemOut(boolean flag){
LogUtil.useSlf4j=!flag;
return this;
}
}