properties 属性文件的实现方式
数据库连接的 properties 配置文件,配置两个数据源信息:
spring.jdbc.driver=com.mysql.cj.jdbc.Driver
spring.jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf8&nullCatalogMeansCurrent=true&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.jdbc.username=root
spring.jdbc.password=123456
mybatis.jdbc.driver=com.mysql.cj.jdbc.Driver
mybatis.jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&nullCatalogMeansCurrent=true&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
mybatis.jdbc.username=root
mybatis.jdbc.password=123456
Mybatis 配置文件,配置两个 environment:
<!-- 引入数据库配置文件 -->
<properties resource="database.properties"/>
<environments default="Spring">
<environment id="Spring">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${spring.jdbc.driver}"/>
<property name="url" value="${spring.jdbc.url}"/>
<property name="username" value="${spring.jdbc.username}"/>
<property name="password" value="${spring.jdbc.password}"/>
</dataSource>
</environment>
<environment id="Mybatis">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${mybatis.jdbc.driver}"/>
<property name="url" value="${mybatis.jdbc.url}"/>
<property name="username" value="${mybatis.jdbc.username}"/>
<property name="password" value="${mybatis.jdbc.password}"/>
</dataSource>
</environment>
</environments>
通过 DataSourceSessionFactory 工具类,获取 SqlSessionFactory:
package cn.worstone.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Optional;
public final class DataSourceSessionFactory {
private static String MybatisConfigPath = "mybatis.xml";
private static Map<DataSourceEnvironment, SqlSessionFactory> sessionFactoryMap = new HashMap<>();
/**
* Mybatis 配置的 Environment 中的数据源的枚举类型(即 Environment 的 id 属性值)
*/
public static enum DataSourceEnvironment {
Spring,
Mybatis;
}
public static SqlSessionFactory getSessionFactory(DataSourceEnvironment dataSourceEnvironment) {
SqlSessionFactory sqlSessionFactory = DataSourceSessionFactory.sessionFactoryMap.get(dataSourceEnvironment);
sqlSessionFactory = Optional.ofNullable(sqlSessionFactory).orElseGet(()->{
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(DataSourceSessionFactory.MybatisConfigPath);
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory temp = new SqlSessionFactoryBuilder().build(inputStream, dataSourceProperties);
sessionFactoryMap.put(dataSourceProperties, temp);
return temp;
});
return sqlSessionFactory;
}
}
扩展
定义一个统一的 Mapper 标识接口,每一个具体的 Mapper 接口继承该接口:
package cn.worstone.mapper;
/**
* 通用的 Mapper 接口, 可以定制所有的通用方法, 设置参数类型为 Map<String, Object>
*/
public interface Mapper {
}
定义一个 Mapper 代理工厂,并获取 Mapper 代理,便于在执行完 Mapper 中的方法后关闭 SqlSession:
package cn.worstone.utils;
import cn.worstone.mapper.Mapper;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public final class MapperFactory {
public static <T> T createMapper(Class<? extends Mapper> clazz, DataSourceSessionFactory.DataSourceEnvironment dataSourceEnvironment, boolean isCommit) {
SqlSessionFactory sqlSessionFactory = MapperFactory.getSqlSessionFactory(dataSourceEnvironment);
SqlSession sqlSession = sqlSessionFactory.openSession(isCommit);
Mapper mapper = sqlSession.getMapper(clazz);
return (T) MapperProxy.bind(mapper, sqlSession);
}
/**
* 默认不提交事务的方法
* @param clazz
* @param dataSourceEnvironment
* @param <T>
* @return
*/
public static <T> T createMapper(Class<? extends Mapper> clazz, DataSourceSessionFactory.DataSourceEnvironment dataSourceEnvironment) {
return MapperFactory.createMapper(clazz, dataSourceEnvironment, false);
}
private static SqlSessionFactory getSqlSessionFactory(DataSourceSessionFactory.DataSourceEnvironment dataSourceEnvironment) {
return DataSourceSessionFactory.getSessionFactory(dataSourceEnvironment);
}
private static class MapperProxy implements InvocationHandler {
private Mapper mapper;
private SqlSession sqlSession;
private MapperProxy(Mapper mapper, SqlSession sqlSession) {
this.mapper = mapper;
this.sqlSession = sqlSession;
}
// 获取代理对象的实例 Java 只能代理接口
public static Mapper bind(Mapper mapper, SqlSession sqlSession) {
return (Mapper) Proxy.newProxyInstance(mapper.getClass().getClassLoader(), mapper.getClass().getInterfaces(), new MapperProxy(mapper, sqlSession));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
result = method.invoke(mapper, args);
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
return result;
}
}
}
测试方法
@Test
public void query() {
UserMapper userMapper = MapperFactory.createMapper(UserMapper.class, DataSourceEnvironment.Spring);
User user = userMapper.query("5f414d80-c413-42d4-8844-909d3e107ce8");
System.out.println(user);
}
@Test
public void add() {
UserMapper userMapper = MapperFactory.createMapper(UserMapper.class, DataSourceEnvironment.Spring, true);
User user = new User(UUID.randomUUID().toString(), "XXX", "Second", "123456");
int num = userMapper.add(user);
System.out.println(num);
}
代码优化
以上的方式就可以支持多个数据源了,但是代码还不够优雅。以上代码,我们发现 DataSourceEnvironment 这个枚举变量在 Mapper 实现类以及 MapperFactory 中传来传去,我们应该尽量减少一个类对其他类的耦合,也就是上面的代码,不符合 Java 编程原则中的 迪米特法则 。
那我们来优化一下代码吧。
将 MapperFactory 设计成枚举策略模式,并且将 :SqlSession 存放到 ThreadLocal 中,减少资源浪费
package cn.worstone.utils;
import cn.worstone.mapper.Mapper;
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 java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Optional;
public enum MapperFactory {
Spring {
private SqlSessionFactory sqlSessionFactory;
private final ThreadLocal<SqlSession> localSession = new ThreadLocal<>();
@Override
public <T> T createMapper(Class<? extends Mapper> clazz, boolean isCommit, boolean isClose) {
return MapperFactory.createMapper(clazz, this, isCommit, isClose);
}
@Override
public <T> T createAutoCommitMapper(Class<? extends Mapper> clazz) {
return MapperFactory.createMapper(clazz, this, true, true);
}
@Override
public <T> T createNotCloseMapper(Class<? extends Mapper> clazz) {
return MapperFactory.createMapper(clazz, this, false, false);
}
@Override
public <T> T createMapper(Class<? extends Mapper> clazz) {
return this.createMapper(clazz, false, true);
}
@Override
protected void createSqlSessionFactory() throws IOException {
// 这个 I/O 流不用手动关, new SqlSessionFactoryBuilder().build() 里面已经关闭了这个流
InputStream inputStream = Resources.getResourceAsStream(MapperFactory.MybatisConfigPath);
this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, this.name());
}
@Override
protected void closeThreadLocal() {
this.localSession.remove();
}
@Override
public ThreadLocal<SqlSession> getThreadLocal() {
return this.localSession;
}
@Override
public SqlSessionFactory getSqlSessionFactory() {
return this.sqlSessionFactory;
}
@Override
public SqlSession getSqlSession() {
return this.getSqlSession(false);
}
@Override
public void closeSqlSession() {
MapperFactory.closeSqlSession(this);
}
@Override
public SqlSession getSqlSession(boolean isCommit) {
return MapperFactory.getSqlSession(this, isCommit);
}
},
Mybatis {
private SqlSessionFactory sqlSessionFactory;
private final ThreadLocal<SqlSession> localSession = new ThreadLocal<>();
@Override
public <T> T createMapper(Class<? extends Mapper> clazz, boolean isCommit, boolean isClose) {
return MapperFactory.createMapper(clazz, this, isCommit, isClose);
}
@Override
public <T> T createAutoCommitMapper(Class<? extends Mapper> clazz) {
return MapperFactory.createMapper(clazz, this, true, true);
}
@Override
public <T> T createNotCloseMapper(Class<? extends Mapper> clazz) {
return MapperFactory.createMapper(clazz, this, false, false);
}
@Override
public <T> T createMapper(Class<? extends Mapper> clazz) {
return this.createMapper(clazz, false, true);
}
@Override
protected void createSqlSessionFactory() throws IOException {
InputStream inputStream = Resources.getResourceAsStream(MapperFactory.MybatisConfigPath);
this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, this.name());
}
@Override
protected void closeThreadLocal() {
this.localSession.remove();
}
@Override
public ThreadLocal<SqlSession> getThreadLocal() {
return this.localSession;
}
@Override
public SqlSessionFactory getSqlSessionFactory() {
return this.sqlSessionFactory;
}
@Override
public SqlSession getSqlSession() {
return this.getSqlSession(false);
}
@Override
public void closeSqlSession() {
MapperFactory.closeSqlSession(this);
}
@Override
public SqlSession getSqlSession(boolean isCommit) {
return MapperFactory.getSqlSession(this, isCommit);
}
};
public abstract <T> T createMapper(Class<? extends Mapper> clazz, boolean isCommit, boolean isClose);
public abstract <T> T createAutoCommitMapper(Class<? extends Mapper> clazz);
public abstract <T> T createNotCloseMapper(Class<? extends Mapper> clazz);
// 默认的 SqlSession 不提交事务, 而且调用一个方法后自动关闭
public abstract <T> T createMapper(Class<? extends Mapper> clazz);
protected abstract void createSqlSessionFactory() throws IOException;
protected abstract void closeThreadLocal();
public abstract ThreadLocal<SqlSession> getThreadLocal();
public abstract SqlSessionFactory getSqlSessionFactory();
public abstract SqlSession getSqlSession(boolean isCommit);
public abstract SqlSession getSqlSession();
public abstract void closeSqlSession();
private static final String MybatisConfigPath = "mybatis.xml";
static {
try {
MapperFactory.Mybatis.createSqlSessionFactory();
MapperFactory.Spring.createSqlSessionFactory();
} catch (IOException e) {
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
private static <T> T createMapper(Class<? extends Mapper> clazz, MapperFactory mapperFactory, boolean isCommit, boolean isClose) {
SqlSession sqlSession = mapperFactory.getSqlSession(isCommit);
Mapper mapper = sqlSession.getMapper(clazz);
if (isClose) {
mapperFactory.closeThreadLocal();
}
return (T) MapperProxy.bind(mapper, sqlSession, isClose);
}
private static SqlSession getSqlSession(MapperFactory mapperFactory, boolean isCommit) {
SqlSession sqlSession = mapperFactory.getThreadLocal().get();
sqlSession = Optional.ofNullable(sqlSession).orElseGet(() -> {
SqlSession temp = mapperFactory.getSqlSessionFactory().openSession(isCommit);
mapperFactory.getThreadLocal().set(temp);
return temp;
});
return sqlSession;
}
private static void closeSqlSession(MapperFactory mapperFactory) {
SqlSession sqlSession = mapperFactory.getThreadLocal().get();
if (sqlSession != null) {
sqlSession.close();
}
mapperFactory.closeThreadLocal();
}
private static class MapperProxy implements InvocationHandler {
private final Mapper mapper;
private final SqlSession sqlSession;
private final boolean isClose;
private MapperProxy(Mapper mapper, SqlSession sqlSession, boolean isClose) {
this.mapper = mapper;
this.sqlSession = sqlSession;
this.isClose = isClose;
}
// 获取代理对象的实例 Java 只能代理接口
public static Mapper bind(Mapper mapper, SqlSession sqlSession, boolean isClose) {
return (Mapper) Proxy.newProxyInstance(mapper.getClass().getClassLoader(), mapper.getClass().getInterfaces(), new MapperProxy(mapper, sqlSession, isClose));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Object result = null;
try {
result = method.invoke(mapper, args);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 默认关闭, 可以手动设置不关闭
if (isClose) {
sqlSession.close();
}
}
return result;
}
}
}
测试方法
@Test
public void query() {
UserMapper userMapper = MapperFactory.Spring.createMapper(UserMapper.class);
User user = userMapper.query("5f414d80-c413-42d4-8844-909d3e107ce8");
System.out.println(user);
}
@Test
public void add() {
UserMapper userMapper = MapperFactory.Spring.createMapper(UserMapper.class, true);
User user = new User(UUID.randomUUID().toString(), "XXX", "Second", "123456");
int num = userMapper.add(user);
System.out.println(num);
}
补充
对于复杂的事务可以采用如下方法进行控制:
SqlSession sqlSession = MapperFactory.Spring.getSqlSessionFactory().openSession(false);
try {
// to do something
// to do something
sqlSession.commit();
} catch (Exception e) {
sqlSession.rollback();
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
通过
new SqlSessionFactoryBuilder().build()方法创建SqlSessionFactory时,不需要关闭参数中的I/O流,因为build()方法中已经将其关闭。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
Properties 属性类型的实现方式
思路:
Mybatis配置文件中<environment>标签设置为动态传参。DataSourceSessionFactory类中构建SqlSessionFactory时,读取DataBaseProperties数据库配置类中的数据库连接信息动态生成。
Mybatis 配置信息:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
DataBaseProperties 数据库配置类:
package cn.worstone.utils;
import java.util.Properties;
public class DataBaseProperties {
public final static Properties Spring;
public final static Properties Mybatis;
static {
Spring = new Properties();
Spring.setProperty("driver", "com.mysql.cj.jdbc.Driver");
Spring.setProperty("url", "jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf8&nullCatalogMeansCurrent=true&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC");
Spring.setProperty("username", "root");
Spring.setProperty("password", "123456");
Mybatis = new Properties();
Mybatis.setProperty("driver", "com.mysql.cj.jdbc.Driver");
Mybatis.setProperty("url", "jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&nullCatalogMeansCurrent=true&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC");
Mybatis.setProperty("username", "root");
Mybatis.setProperty("password", "123456");
}
}
DataSourceSessionFactory 类以及测试方法:
package cn.worstone.utils;
import cn.worstone.bean.User;
import cn.worstone.mapper.UserMapper;
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 java.io.IOException;
import java.io.InputStream;
import java.util.*;
public final class DataSourceSessionFactory {
private static String MybatisConfigPath = "mybatis.xml";
private static Map<Properties, SqlSessionFactory> sessionFactoryMap = new HashMap<>();
public static SqlSessionFactory getSessionFactory(Properties dataSourceProperties) {
SqlSessionFactory sqlSessionFactory = DataSourceSessionFactory.sessionFactoryMap.get(dataSourceProperties);
sqlSessionFactory = Optional.ofNullable(sqlSessionFactory).orElseGet(()->{
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(DataSourceSessionFactory.MybatisConfigPath);
} catch (IOException e) {
e.printStackTrace();
}
return new SqlSessionFactoryBuilder().build(inputStream, dataSourceProperties);
});
sessionFactoryMap.put(dataSourceProperties, sqlSessionFactory);
return sqlSessionFactory;
}
public static void main(String[] args) {
SqlSession sqlSession = DataSourceSessionFactory.getSessionFactory(DataBaseProperties.Spring).openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.queryList();
sqlSession.close();
for (User user : userList) {
System.out.println(user);
}
}
}
934



被折叠的 条评论
为什么被折叠?



