/* * 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.datasource.dynamic; import com.atomikos.jdbc.AtomikosDataSourceBean; import com.atomikos.jdbc.AtomikosSQLException; import org.hsweb.commons.StringUtils; import org.hsweb.concurrent.lock.LockFactory; import org.hsweb.web.bean.po.datasource.DataSource; import org.hsweb.web.core.datasource.DatabaseType; 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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; import java.io.Closeable; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReadWriteLock; @Service("dynamicDataSourceService") public class DynamicDataSourceServiceImpl implements DynamicDataSourceService { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Resource private DataSourceService dataSourceService; @Autowired(required = false) protected DynamicDataSource dynamicDataSource; @Autowired private LockFactory lockFactory; private ConcurrentMap<String, CacheInfo> cache = new ConcurrentHashMap<>(); @Override public javax.sql.DataSource getDataSource(String id) { return getCache(id).getDataSource(); } @Override public String getDataBaseType(String id) { return getCache(id).getDatabaseType().name(); } @Override @PreDestroy public void destroyAll() throws Exception { cache.values().stream().map(CacheInfo::getDataSource).forEach(this::closeDataSource); cache.clear(); } protected void closeDataSource(javax.sql.CommonDataSource ds) { if (ds instanceof AtomikosDataSourceBean) { closeDataSource(((AtomikosDataSourceBean) ds).getXaDataSource()); ((AtomikosDataSourceBean) ds).close(); } else if (ds instanceof Closeable) { try { ((Closeable) ds).close(); } catch (IOException e) { logger.error("close datasource error", e); } } } protected CacheInfo getCache(String id) { DynamicDataSource.useDefault(); try { DataSource old = dataSourceService.selectByPk(id); if (old == null || old.getEnabled() != 1) throw new NotFoundException("数据源不存在或已禁用"); //创建锁 ReadWriteLock readWriteLock = lockFactory.createReadWriteLock("dynamic.ds." + id); readWriteLock.readLock().tryLock(); CacheInfo cacheInfo = null; try { cacheInfo = cache.get(id); // 缓存存在,并且hash一致 if (cacheInfo != null && cacheInfo.getHash() == old.getHash()) return cacheInfo; } finally { try { readWriteLock.readLock().unlock(); } catch (Exception e) { } } readWriteLock.writeLock().tryLock(); try { if (cacheInfo != null) { closeDataSource(cacheInfo.getDataSource()); } //加载datasource到缓存 javax.sql.DataSource dataSource = createDataSource(old); DatabaseType databaseType = DatabaseType.fromJdbcUrl(old.getUrl()); cacheInfo = new CacheInfo(old.getHash(), dataSource, databaseType); cache.put(id, cacheInfo); } finally { try { readWriteLock.writeLock().unlock(); } catch (Exception e) { } } return cacheInfo; } finally { DynamicDataSource.useLast(); } } protected javax.sql.DataSource createDataSource(DataSource dataSource) { DynamicDataSourceProperties properties = new DynamicDataSourceProperties(); properties.setName("ds_" + dataSource.getId()); properties.setBeanClassLoader(this.getClass().getClassLoader()); properties.setUsername(dataSource.getUsername()); properties.setPassword(dataSource.getPassword()); properties.setUrl(dataSource.getUrl()); properties.setType(DatabaseType.fromJdbcUrl(dataSource.getUrl())); properties.setTestQuery(dataSource.getTestSql()); Map<String, Object> otherProperties = dataSource.getProperties(); int initTimeout = StringUtils.toInt(otherProperties.getOrDefault("initTimeOut", 30 * 1000)); otherProperties.remove("initTimeOut"); try { properties.afterPropertiesSet(); } catch (Exception e) { throw new RuntimeException(e); } if (otherProperties != null) { properties.getProperties().putAll(otherProperties); } else { properties.initDefaultProperties(); } AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean(); properties.putProperties(dataSourceBean); boolean[] success = new boolean[1]; //异步初始化 new Thread(() -> { try { dataSourceBean.init(); success[0] = true; } catch (AtomikosSQLException e) { logger.error("创建数据源失败", e); closeDataSource(dataSourceBean); cache.remove(dataSource.getId()); } }).start(); //初始化检测 new Thread(() -> { try { Thread.sleep(initTimeout); if (!success[0]) { logger.error("初始化jdbc超时:{}", dataSourceBean); try { closeDataSource(dataSourceBean); } finally { cache.remove(dataSource.getId()); } } } catch (Exception e) { } }).start(); return dataSourceBean; } @PostConstruct public void init() { if (null != dynamicDataSource && dynamicDataSource instanceof DynamicXaDataSourceImpl) ((DynamicXaDataSourceImpl) dynamicDataSource).setDynamicDataSourceService(this); } class CacheInfo { int hash; DatabaseType databaseType; javax.sql.DataSource dataSource; public CacheInfo(int hash, javax.sql.DataSource dataSource, DatabaseType type) { this.hash = hash; this.dataSource = dataSource; this.databaseType = type; } public int getHash() { return hash; } public javax.sql.DataSource getDataSource() { return dataSource; } public DatabaseType getDatabaseType() { return databaseType; } } }