Mybatis動態資料來源實現
Mybatis資料庫檔案配置是在專案啟動時初始化資料工廠的,初始化過程僅為1次,當資料庫地址改變時需修改配置檔案重新啟動專案,無法動態載入資料來源。
Mybatis連線資料庫底層核心庫SqlSessionFactory,專案初始化也是生成該類,並快取,該需求需要通過程式設計根據不同資料來源動態生成SqlSessionFactory例項。
核心程式碼:
String driver="oracle.jdbc.driver.OracleDriver",url="jdbc:oracle:thin:@127.0.0.1:1521:orcl";
///mysql
//driver = "com.mysql.jdbc.Driver" ;
//url = "jdbc:mysql://"+hotel.getIp()+":"+hotel.getPort()+"/"+hotel.getDatabase();
//初始化資料庫屬性
Properties properties = new Properties();
properties.setProperty("jdbc.driver",driver);
properties.setProperty("jdbc.url", url);
properties.setProperty("jdbc.username" ,hotel.getUsername());
properties.setProperty("jdbc.password",hotel.getPassword());
Reader reader = Resources.getResourceAsReader(HotelFactory.configuration);
//建立資料工廠
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build (reader, properties);
SqlSession sqlSession = sqlSessionFactory.openSession();
HotelMapper hotelMapper = sqlSession.getMapper(HotelMapper.class);
hotelMapper.getHotelById(1);
//釋放會話
sqlSession.clearCache();
sqlSession.close();
configuration.xml Mybatis配置檔案
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<!--給實體類起一個別名 user
<typeAlias type="entity.Hotel" alias="Hotel" />
<typeAlias type="entity.Room" alias="Room" />
-->
</typeAliases>
<!--資料來源配置 使用mysql資料庫 -->
<environments default="HD">
<environment id="HD">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<!-- 併發最大連線數 預設10-->
<property name="poolMaximumActiveConnections" value="1000"/>
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
<!-- <environment id="HO">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@192.168.1.17:1521:orcl" />
<property name="username" value="hotel" />
<property name="password" value="q123" />
</dataSource>
</environment> -->
</environments>
<mappers>
<!-- userMapper.xml裝載進來 同等於把“dao”的實現裝載進來 -->
<mapper resource="mapper/HotelMapper.xml"/>
<mapper resource="mapper/RestaurantMapper.xml"/>
<mapper resource="mapper/RoomsMapper.xml"/>
<mapper resource="mapper/MeetingsMapper.xml"/>
</mappers>
</configuration>
HotelMapper
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.HotelMapper">
<select id="getHotelById" resultType="entity.Hotel">
select * from HOTEL
where ID = #{id,jdbcType=DECIMAL}
</select>
</mapper>
public interface HotelMapper {
Hotel getHotelById(@Param("id") int id);
}
SqlSessionFactory只是記載的資料庫的連線屬性,並未與資料庫連線,並不佔用資料庫及系統資源。
SQLSession便於資料庫建立連線了,每次讀取資料後若不釋放,資料庫的連線數不斷增加,當超過資料庫的最大連線數或者記憶體溢位均會導致程式崩潰。
每次連線都需要初始化,過於麻煩,可封裝套資料工廠,快取相關資訊。
設計思路1:快取SqlSessionFactory,通過java動態代理釋放資料庫資源即SqlSession。(適用多點,SqlSession數量不可控情況)
設計思路2:快取SqlSessionFactory及SqlSession,SqlSession會預設快取已呼叫的方法,通過sqlSession.clearCache()清空預設快取,保證讀取的是實時資料庫。(適用單點或少數站點機,SqlSession數量可控情況)
設計思路1核心程式碼:
資料工廠DataSourceFactory程式碼:
package factory;
import java.io.Reader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import entity.Hotel;
@SuppressWarnings("finally")
public class DataSourceFactory {
//酒店資料工作快取數量
private static final int MaxDataSourceSize=10;
//埠超時時間
private static final int TimeOut = 2000;
//酒店資料工廠快取
private static Map<Integer,SqlSessionFactory> dataSoruce;
static{
dataSoruce = new HashMap<Integer, SqlSessionFactory>(MaxDataSourceSize);
}
//刪除第一個元素
private static void removeFirstMap(){
for (Integer key : dataSoruce.keySet()) {
dataSoruce.remove(key);
break;
}
}
//刪除酒店快取
public static void removeHotel(int hotelid){
dataSoruce.remove(hotelid);
}
//檢測連線是否可用
public static boolean isConnection(Hotel hotel){
Boolean result = false;
try{
//檢查埠是否開放
Socket client = new Socket();
SocketAddress socketAddress = new InetSocketAddress(hotel.getIp(),Integer.parseInt(hotel.getPort()));
client.connect(socketAddress,TimeOut);
client.close();
result = true;
}
catch(Exception e){
e.printStackTrace();
result = false;
}
finally{
return result;
}
}
public static boolean isConnection(Integer id){
Boolean result = false;
try{
Hotel hotel = HotelFactory.getHotelById(id);
if(hotel != null){
//檢查埠是否開放
Socket client = new Socket();
SocketAddress socketAddress = new InetSocketAddress(hotel.getIp(),Integer.parseInt(hotel.getPort()));
client.connect(socketAddress,TimeOut);
client.close();
result = true;
}
}
catch(Exception e){
e.printStackTrace();
result = false;
}
finally{
return result;
}
}
private static SqlSessionFactory createSqlSessionFactory(Integer id){
SqlSessionFactory _sqlSessionFactory = null;
try{
Hotel hotel = HotelFactory.getHotelById(id);
if(hotel != null && isConnection(hotel)){
//資料庫匹配 Oracle/Mysql
String driver="oracle.jdbc.driver.OracleDriver",url="jdbc:oracle:thin:@"+hotel.getIp()+":"+hotel.getPort()+":"+hotel.getSid();
///mysql
//driver = "com.mysql.jdbc.Driver";
//url = "jdbc:mysql://"+hotel.getIp()+":"+hotel.getPort()+"/"+hotel.getDatabase();
//初始化資料庫屬性
Properties properties = new Properties();
properties.setProperty("jdbc.driver",driver);
properties.setProperty("jdbc.url", url);
properties.setProperty("jdbc.username",hotel.getUsername());
properties.setProperty("jdbc.password",hotel.getPassword());
Reader reader = Resources.getResourceAsReader(HotelFactory.configuration);
//建立資料工廠
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
_sqlSessionFactory = builder.build(reader, properties);
}
}
catch(Exception e){
e.printStackTrace();
}
finally{
return _sqlSessionFactory;
}
}
//根據酒店id獲取Mapper
@SuppressWarnings("unchecked")
public static <T> T getMapper(Integer id,Class<?> clazz){
if(isConnection(id)){
SqlSessionFactory sqlSessionFactory = dataSoruce.get(id);
if(sqlSessionFactory==null){
//建立資料工廠
sqlSessionFactory = createSqlSessionFactory(id);
if(sqlSessionFactory != null){
//排序演算法 刪除Map序列第一個元素,並將當前元素移至Map序列的首位
if(dataSoruce.size()>=MaxDataSourceSize){
removeFirstMap();
}
dataSoruce.put(id, sqlSessionFactory);
}
else
return null;
}
/*else{
//排序演算法 將當前元素移至Map序列的首位
dataSoruce.remove(id);
dataSoruce.put(id, sqlSessionFactory);
}*/
SqlSession sqlSession = sqlSessionFactory.openSession();
Object idal = sqlSession.getMapper(clazz);
return (T)IDALProxy.bind(idal, sqlSession);
}
else
return null;
}
//動態載入 SqlSession提交,釋放
public static class IDALProxy implements InvocationHandler {
private Object idal;
private SqlSession sqlSession;
private IDALProxy(Object idal, SqlSession sqlSession) {
this.idal = idal;
this.sqlSession = sqlSession;
}
public static Object bind(Object idal, SqlSession sqlSession) {
return Proxy.newProxyInstance(idal.getClass().getClassLoader(),idal.getClass().getInterfaces(), new IDALProxy(idal, sqlSession));
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object = null;
try {
object = method.invoke(idal, args);
} catch(Exception e) {
sqlSession.rollback();
e.printStackTrace();
} finally {
sqlSession.commit();
sqlSession.clearCache();
sqlSession.close();
}
return object;
}
}
}
酒店工廠HotelFactory程式碼:
package factory;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;
import mapper.HotelMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import entity.Hotel;
public class HotelFactory {
public static SqlSessionFactory sqlSessionFactory = null;
//資料庫屬性值
public static final String jdbc_properties="jdbc.properties";
//資料庫配置檔案
public final static String configuration = "configuration.xml";
//建立本地酒店管理資料庫
static{
try {
Properties properties = new Properties();
InputStream in = Resources.getResourceAsStream(jdbc_properties);
properties.load(in);
String driver = properties.getProperty("jdbc.driverClassName");
String url = properties.getProperty("jdbc.url");
String username = properties.getProperty("jdbc.username");
String password = properties.getProperty("jdbc.password");
properties.setProperty("jdbc.driver",driver);
properties.setProperty("jdbc.url", url);
properties.setProperty("jdbc.username",username);
properties.setProperty("jdbc.password",password);
Reader reader = Resources.getResourceAsReader(configuration);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
sqlSessionFactory = builder.build(reader, properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//根據id獲取酒店實體
@SuppressWarnings("finally")
public static Hotel getHotelById(Integer id){
Hotel hotel = null;
SqlSession sqlSession = null;
try{
sqlSession = sqlSessionFactory.openSession();
hotel = sqlSession.getMapper(HotelMapper.class).getHotelById(id);
}
catch(Exception e){
e.printStackTrace();
}
finally{
if(sqlSession != null)
sqlSession.close();
return hotel;
}
}
}
引用例項:
RoomsMapper roomMapper = getMapper(hotelid);
roomMapper.getRoom();
Hotel實體中存了資料庫的IP地址,埠號等基本資訊,根據hotel生成對應庫的Mapper例項進行資料操作。
通過IDALProxy類對生成Mapper例項的所有方法進行了再次封裝,實現資料庫資源的提交,快取清空和釋放。