新增动态数据源

This commit is contained in:
zhouhao
2016-08-25 18:02:20 +08:00
parent 474ef7b6c7
commit d778b92d4a
9 changed files with 262 additions and 95 deletions

View File

@@ -9,6 +9,7 @@ import org.hsweb.ezorm.render.SqlAppender;
import org.hsweb.ezorm.render.SqlRender;
import org.hsweb.web.bean.po.user.User;
import org.hsweb.web.core.authorize.annotation.Authorize;
import org.hsweb.web.core.datasource.DynamicDataSource;
import org.hsweb.web.core.exception.AuthorizeException;
import org.hsweb.web.core.exception.AuthorizeForbiddenException;
import org.hsweb.web.core.logger.annotation.AccessLogger;
@@ -61,15 +62,19 @@ public class DatabaseManagerController {
return ResponseMessage.ok(dataBaseManagerService.createCreateSql(createTableMetaDataByJson(jsonObject)));
}
@RequestMapping(value = "/tables/{dataSourceId}", method = RequestMethod.GET)
@Authorize(action = "R")
@AccessLogger("指定数据源获取表结构")
public ResponseMessage showTables(@PathVariable("dataSourceId") String dataSourceId) throws SQLException {
return ResponseMessage.ok(dataBaseManagerService.getTableList(dataSourceId))
.include(TableMetaData.class, "name", "alias", "comment", "fields")
.include(FieldMetaData.class, "name", "alias", "comment", "dataType", "properties")
.onlyData();
try {
DynamicDataSource.use(dataSourceId);
return ResponseMessage.ok(dataBaseManagerService.getTableList())
.include(TableMetaData.class, "name", "alias", "comment", "fields")
.include(FieldMetaData.class, "name", "alias", "comment", "dataType", "properties")
.onlyData();
} finally {
DynamicDataSource.useDefault(false);
}
}
public List<String> buildSqlList(String sql) {
@@ -104,20 +109,34 @@ public class DatabaseManagerController {
@RequestMapping(value = "/exec/{dataSourceId}", method = RequestMethod.POST)
@AccessLogger("指定数据源执行SQL")
public ResponseMessage exec(@PathVariable("dataSourceId") String dataSourceId, @RequestBody String sql) throws Exception {
return ResponseMessage.ok(dataBaseManagerService.execSql(dataSourceId, buildSqlList(sql)));
DynamicDataSource.use(dataSourceId);
try {
return ResponseMessage.ok(dataBaseManagerService.execSql(buildSqlList(sql)));
} finally {
DynamicDataSource.useDefault(false);
}
}
@RequestMapping(value = "/sql/alter/{dataSourceId}", method = RequestMethod.POST)
@AccessLogger("指定数据源查询修改表结构SQL")
public ResponseMessage showAlterSql(@PathVariable("dataSourceId") String dataSourceId, @RequestBody JSONObject jsonObject) throws Exception {
return ResponseMessage.ok(dataBaseManagerService.createAlterSql(dataSourceId, createTableMetaDataByJson(jsonObject)));
try {
DynamicDataSource.use(dataSourceId);
return ResponseMessage.ok(dataBaseManagerService.createAlterSql(createTableMetaDataByJson(jsonObject)));
} finally {
DynamicDataSource.useDefault(false);
}
}
@RequestMapping(value = "/sql/create/{dataSourceId}", method = RequestMethod.POST)
@AccessLogger("指定数据源查询创建表结构SQL")
public ResponseMessage showCreateSql(@PathVariable("dataSourceId") String dataSourceId, @RequestBody JSONObject jsonObject) throws Exception {
return ResponseMessage.ok(dataBaseManagerService.createCreateSql(dataSourceId, createTableMetaDataByJson(jsonObject)));
try {
DynamicDataSource.use(dataSourceId);
return ResponseMessage.ok(dataBaseManagerService.createCreateSql(createTableMetaDataByJson(jsonObject)));
} finally {
DynamicDataSource.useDefault(false);
}
}
protected TableMetaData createTableMetaDataByJson(JSONObject jsonObject) {

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2015-2016 http://hsweb.me
*
* 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 org.hsweb.web.core.datasource;
import org.hsweb.web.core.utils.ThreadLocalUtils;
import javax.sql.DataSource;
/**
* @author zhouhao
*/
public interface DynamicDataSource extends DataSource {
String DATA_SOURCE_FLAG = "data-source-id";
String DATA_SOURCE_FLAG_LAST = "data-source-id-last";
static void useLast() {
use(ThreadLocalUtils.get(DATA_SOURCE_FLAG_LAST));
}
static void use(String dataSourceId) {
ThreadLocalUtils.put(DATA_SOURCE_FLAG, dataSourceId);
}
static String getActiveDataSourceId() {
return ThreadLocalUtils.get(DATA_SOURCE_FLAG);
}
static void useDefault(boolean rememberLast) {
if (getActiveDataSourceId() != null && rememberLast)
ThreadLocalUtils.put(DATA_SOURCE_FLAG_LAST, getActiveDataSourceId());
ThreadLocalUtils.remove(DATA_SOURCE_FLAG);
}
static void useDefault() {
useDefault(true);
}
DataSource getActiveDataSource();
}

View File

@@ -22,6 +22,11 @@
<artifactId>hsweb-web-dao-interface</artifactId>
</dependency>
<dependency>
<groupId>org.hsweb</groupId>
<artifactId>hsweb-web-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2015-2016 http://hsweb.me
*
* 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 org.hsweb.web.service.impl;
import org.hsweb.web.service.impl.datasource.DynamicDataSourceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
* Created by zhouhao on 16-4-20.
*/
@Configuration
public class DynamicDataSourceAutoConfiguration {
@Autowired
private DataSourceProperties properties;
@Bean
@ConfigurationProperties(prefix = DataSourceProperties.PREFIX)
public DataSource dataSource() {
DataSourceBuilder factory = DataSourceBuilder
.create(this.properties.getClassLoader())
.driverClassName(this.properties.getDriverClassName())
.url(this.properties.getUrl()).username(this.properties.getUsername())
.password(this.properties.getPassword());
if (this.properties.getType() != null) {
factory.type(this.properties.getType());
}
return new DynamicDataSourceImpl(factory.build());
}
}

View File

@@ -25,19 +25,17 @@ import org.hsweb.web.bean.po.datasource.DataSource;
import org.hsweb.web.core.exception.BusinessException;
import org.hsweb.web.dao.datasource.DataSourceMapper;
import org.hsweb.web.service.config.ConfigService;
import org.hsweb.web.service.impl.AbstractServiceImpl;
import org.hsweb.web.service.datasource.DataSourceService;
import org.hsweb.web.service.impl.AbstractServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.sql.Connection;
@@ -54,15 +52,15 @@ public class DataSourceServiceImpl extends AbstractServiceImpl<DataSource, Strin
private static final String CACHE_NAME = "datasource";
@Autowired
protected DataSourceProperties properties;
@Resource
protected DataSourceMapper dataSourceMapper;
@Resource
protected ConfigService configService;
@Autowired
protected javax.sql.DataSource defaultDataSource;
@Override
protected DataSourceMapper getMapper() {
return this.dataSourceMapper;
@@ -76,12 +74,13 @@ public class DataSourceServiceImpl extends AbstractServiceImpl<DataSource, Strin
.url(dataSource.getUrl())
.username(dataSource.getUsername())
.password(dataSource.getPassword())
.type(defaultDataSource.getClass());
.type(properties.getType());
return builder.build();
}
@Override
@Cacheable(value = CACHE_NAME, key = "'id:'+#id")
@Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED)
public DataSource selectByPk(String id) {
return super.selectByPk(id);
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2015-2016 http://hsweb.me
*
* 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 org.hsweb.web.service.impl.datasource;
import org.hsweb.web.core.datasource.DynamicDataSource;
import org.hsweb.web.service.datasource.DynamicDataSourceService;
import org.springframework.jdbc.datasource.AbstractDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class DynamicDataSourceImpl extends AbstractDataSource implements DynamicDataSource {
private javax.sql.DataSource defaultDataSource;
private DynamicDataSourceService dynamicDataSourceService;
public DynamicDataSourceImpl(javax.sql.DataSource defaultDataSource) {
this.defaultDataSource = defaultDataSource;
}
@Override
public Connection getConnection() throws SQLException {
return getActiveDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return getActiveDataSource().getConnection(username, password);
}
public DataSource getActiveDataSource() {
String sourceId = DynamicDataSource.getActiveDataSourceId();
if (sourceId == null || dynamicDataSourceService == null) return defaultDataSource;
DataSource dataSource = dynamicDataSourceService.getDataSource(sourceId);
if (dataSource == null) return defaultDataSource;
return dataSource;
}
public void setDefaultDataSource(DataSource defaultDataSource) {
this.defaultDataSource = defaultDataSource;
}
public void setDynamicDataSourceService(DynamicDataSourceService dynamicDataSourceService) {
this.dynamicDataSourceService = dynamicDataSourceService;
}
}

View File

@@ -19,6 +19,7 @@ package org.hsweb.web.service.impl.datasource;
import org.hsweb.concurrent.lock.LockFactory;
import org.hsweb.ezorm.executor.SqlExecutor;
import org.hsweb.web.bean.po.datasource.DataSource;
import org.hsweb.web.core.datasource.DynamicDataSource;
import org.hsweb.web.core.exception.NotFoundException;
import org.hsweb.web.service.datasource.DataSourceService;
import org.hsweb.web.service.datasource.DynamicDataSourceService;
@@ -26,6 +27,7 @@ import org.hsweb.web.service.impl.basic.SqlExecutorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -37,6 +39,9 @@ public class DynamicDataSourceServiceImpl implements DynamicDataSourceService {
@Resource
private DataSourceService dataSourceService;
@Autowired
private DynamicDataSource dynamicDataSource;
@Autowired
private LockFactory lockFactory;
@@ -53,31 +58,41 @@ public class DynamicDataSourceServiceImpl implements DynamicDataSourceService {
}
protected CacheInfo getCache(String id) {
DataSource old = dataSourceService.selectByPk(id);
if (old == null || old.getEnabled() != 1) throw new NotFoundException("数据源不存在或已禁用");
//创建锁
ReadWriteLock readWriteLock = lockFactory.createReadWriteLock("datasource.lock." + id);
readWriteLock.readLock().tryLock();
DynamicDataSource.useDefault();
try {
CacheInfo cacheInfo = cache.get(id);
// 缓存存在,并且hash一致
if (cacheInfo != null && cacheInfo.getHash() == old.getHash())
DataSource old = dataSourceService.selectByPk(id);
if (old == null || old.getEnabled() != 1) throw new NotFoundException("数据源不存在或已禁用");
//创建锁
ReadWriteLock readWriteLock = lockFactory.createReadWriteLock("datasource.lock." + id);
readWriteLock.readLock().tryLock();
try {
CacheInfo cacheInfo = cache.get(id);
// 缓存存在,并且hash一致
if (cacheInfo != null && cacheInfo.getHash() == old.getHash())
return cacheInfo;
} finally {
readWriteLock.readLock().unlock();
}
//加载datasource到缓存
readWriteLock.writeLock().tryLock();
try {
javax.sql.DataSource dataSource = dataSourceService.createDataSource(id);
CacheInfo cacheInfo = new CacheInfo(old.getHash(), dataSource);
cache.put(id, cacheInfo);
return cacheInfo;
} finally {
readWriteLock.writeLock().unlock();
}
} finally {
readWriteLock.readLock().unlock();
}
//加载datasource到缓存
readWriteLock.writeLock().tryLock();
try {
javax.sql.DataSource dataSource = dataSourceService.createDataSource(id);
CacheInfo cacheInfo = new CacheInfo(old.getHash(), dataSource);
cache.put(id, cacheInfo);
return cacheInfo;
} finally {
readWriteLock.writeLock().unlock();
DynamicDataSource.useLast();
}
}
@PostConstruct
public void init() {
((DynamicDataSourceImpl) dynamicDataSource).setDynamicDataSourceService(this);
}
class CacheInfo {
int hash;

View File

@@ -15,22 +15,18 @@ import org.hsweb.ezorm.render.dialect.H2DatabaseMeta;
import org.hsweb.ezorm.render.dialect.MysqlDatabaseMeta;
import org.hsweb.ezorm.render.dialect.OracleDatabaseMeta;
import org.hsweb.ezorm.render.support.simple.SimpleSQL;
import org.hsweb.ezorm.run.simple.SimpleDatabase;
import org.hsweb.web.core.Install;
import org.hsweb.web.core.datasource.DynamicDataSource;
import org.hsweb.web.core.exception.BusinessException;
import org.hsweb.web.core.exception.NotFoundException;
import org.hsweb.web.service.datasource.DataSourceService;
import org.hsweb.web.service.datasource.DynamicDataSourceService;
import org.hsweb.web.service.impl.DatabaseMetaDataFactoryBean;
import org.hsweb.web.service.system.DataBaseManagerService;
import org.hsweb.web.service.system.SqlExecuteProcess;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.LinkedList;
@@ -51,24 +47,16 @@ public class DataBaseManagerServiceImpl implements DataBaseManagerService {
@Resource
private SqlExecutor sqlExecutor;
@Autowired(required = false)
private TableMetaParser tableMetaParser;
@Autowired
private DatabaseMetaDataFactoryBean databaseMetaDataFactoryBean;
@Resource
private DynamicDataSourceService dynamicDataSourceService;
@Resource
private DataSourceService dataSourceService;
@Autowired
private DataSourceProperties dataSourceProperties;
@Override
@Transactional(readOnly = true)
public List<TableMetaData> getTableList() throws SQLException {
if (tableMetaParser == null) {
throw new BusinessException("不支持的数据库");
}
return tableMetaParser.parseAll();
return getDBType().getTableMetaParser(sqlExecutor).parseAll();
}
@Override
@@ -77,6 +65,7 @@ public class DataBaseManagerServiceImpl implements DataBaseManagerService {
return execSql(sqlExecutor, sqlList);
}
@Transactional(rollbackFor = Throwable.class)
public List<Map<String, Object>> execSql(SqlExecutor sqlExecutor, List<String> sqlList) throws SQLException {
List<Map<String, Object>> response = new LinkedList<>();
for (String s : sqlList) {
@@ -115,7 +104,7 @@ public class DataBaseManagerServiceImpl implements DataBaseManagerService {
@Override
public String createAlterSql(TableMetaData newTable) throws Exception {
return createAlterSql(databaseMetaDataFactoryBean.getObject(), tableMetaParser, newTable);
return createAlterSql(getDBType().getDatabaseMetaData(), getDBType().getTableMetaParser(sqlExecutor), newTable);
}
public String createAlterSql(DatabaseMetaData databaseMetaData, TableMetaParser tableMetaParser, TableMetaData newTable) throws Exception {
@@ -131,7 +120,7 @@ public class DataBaseManagerServiceImpl implements DataBaseManagerService {
@Override
public String createCreateSql(TableMetaData newTable) throws Exception {
return createCreateSql(databaseMetaDataFactoryBean.getObject(), newTable);
return createCreateSql(getDBType().getDatabaseMetaData(), newTable);
}
public String createCreateSql(DatabaseMetaData databaseMetaData, TableMetaData newTable) throws Exception {
@@ -144,34 +133,13 @@ public class DataBaseManagerServiceImpl implements DataBaseManagerService {
return builder.toString();
}
@Override
public List<TableMetaData> getTableList(String datasourceId) throws SQLException {
SqlExecutor sqlExecutor = dynamicDataSourceService.getSqlExecutor(datasourceId);
DBType dbType = getDBType(datasourceId);
return dbType.getTableMetaParser(sqlExecutor).parseAll();
}
@Override
@Transactional(rollbackFor = Throwable.class)
public List<Map<String, Object>> execSql(String datasourceId, List<String> sqlList) throws SQLException {
return execSql(dynamicDataSourceService.getSqlExecutor(datasourceId), sqlList);
}
@Override
public String createAlterSql(String datasourceId, TableMetaData newTable) throws Exception {
DBType dbType = getDBType(datasourceId);
SqlExecutor sqlExecutor = dynamicDataSourceService.getSqlExecutor(datasourceId);
return createAlterSql(dbType.getDatabaseMetaData(), dbType.getTableMetaParser(sqlExecutor), newTable);
}
@Override
public String createCreateSql(String datasourceId, TableMetaData newTable) throws Exception {
return createCreateSql(getDBType(datasourceId).getDatabaseMetaData(), newTable);
}
public DBType getDBType(String datasourceId) {
org.hsweb.web.bean.po.datasource.DataSource dataSource = dataSourceService.selectByPk(datasourceId);
String driver = dataSource.getDriver();
public DBType getDBType() {
String datasourceId = DynamicDataSource.getActiveDataSourceId();
String driver = dataSourceProperties.getDriverClassName();
if (datasourceId != null) {
org.hsweb.web.bean.po.datasource.DataSource dataSource = dataSourceService.selectByPk(datasourceId);
driver = dataSource.getDriver();
}
if (driver.contains("mysql")) {
return DBType.mysql;
}
@@ -210,7 +178,8 @@ public class DataBaseManagerServiceImpl implements DataBaseManagerService {
databaseMetaData.init();
return databaseMetaData;
}
}, h2 {
},
h2 {
@Override
public TableMetaParser getTableMetaParser(SqlExecutor sqlExecutor) {
return new H2TableMetaParser(sqlExecutor);

View File

@@ -26,12 +26,4 @@ public interface DataBaseManagerService {
String createAlterSql(TableMetaData newTable) throws Exception;
String createCreateSql(TableMetaData newTable) throws Exception;
List<TableMetaData> getTableList(String datasourceId) throws SQLException;
List<Map<String, Object>> execSql(String datasourceId, List<String> sqlList) throws SQLException;
String createAlterSql(String datasourceId, TableMetaData newTable) throws Exception;
String createCreateSql(String datasourceId, TableMetaData newTable) throws Exception;
}