前言
Mybatis是一个ORM框架, 将数据库表和字段映射为java对象和属性.
1. 如果不使用ORM框架
原生的JDBC, Java Database Connectivity是用来规范客户端程序如何来访问数据库的接口,提供了诸如查询和更新数据库中数据的方法。
组成
-
主要在java.sql包中, DriverManager:负责加载各种不同驱动程序(Driver),并根据不同的请求,向调用者返回相应的数据库连接(Connection)。
-
Driver:驱动程序,会将自身加载到DriverManager中去,并处理相应的请求并返回相应的数据库连接(Connection)。比如需要连接到mysql时, 导入mysql-connector-java, 这是Mysql提供的java如何连接驱动的jar包
-
Connection:数据库连接,负责与进行数据库间通讯,SQL执行以及事务处理都是在某个特定Connection环境中进行的。可以产生用以执行SQL的Statement。
-
Statement:用以执行SQL查询和更新(针对静态SQL语句和单次执行)。
-
PreparedStatement:用以执行包含动态参数的SQL查询和更新(在服务器端编译,允许重复执行以提高效率)。
-
CallableStatement:用以调用数据库中的存储过程。
-
SQLException:代表在数据库连接的建立和关闭和SQL语句的执行过程中发生了例外情况(即错误)。
使用五大步骤:
- 加载(注册)数据库
Class.forName("com.mysql.cj.jdbc.Driver");
类加载之后, 在Driver的实现类的静态代码块中有下面的语句, 自动注册到DriverManager中
static {
DriverManager.registerDriver(new Driver());
}
JDBC4.0之前,连接数据库的时候,通常会用Class.forName("com.mysql.cj.jdbc.Driver")
这句先加载数据库相关的驱动,然后再进行获取连接等的操作。而JDBC4.0之后不需要Class.forName
来加载驱动,直接获取连接即可,这里使用了Java的SPI扩展机制来实现。
- SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制, 比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。
- 建立连接
Connection con = DriverManager.getConnection(url,"myLogin","myPassword");
注: DriverManager静态代码块中有一个loadInitialDrivers(), 遍历使用SPI获取到的具体实现,实例化各个实现类。在遍历的时候,首先调用driversIterator.hasNext()
方法,这里会搜索classpath下以及jar包中所有的META-INF/services目录下的java.sql.Driver文件,加载文件中的实现类。
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
- 执行SQL语句
Statement statement = con.createStatement();
ResultSet result = statement.excuteQuery("select * from user");
- 处理结果集
while(result.next()){
Integer id = result.getInt(1);
String name = result.getString(2);
userList.add(new User(id, name));
}
-
关闭数据库
con.close();
注: 以上每条语句都需要进行try catch, DBUtil是使用模板模式封装的JDBC, 简化了一些操作
- spring实现的JdbcTemplate;
使用Mybatis实现
// 这里是手写读取注解的动态代理实现自动orm映射
interface UserMapper {
@Select("select * from user where id = #{id}")
List<User> selectUserList(Integer id, String name);
}
public class Test {
public static void main(String[] args) {
UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(Test.class.getClassLoader(), new Class<?>[]{UserMapper.class}
, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Select annotation = method.getAnnotation(Select.class);
Map<String, Object> nameArgMap = buildMethodArgNameMap(method, args);
if (annotation != null) {
String[] value = annotation.value();
String sql = value[0];
sql = parseSQL(sql, nameArgMap);
System.out.println(sql);
}
return null;
}
});
userMapper.selectUserList(1, "test");
}
public static String parseSQL(String sql, Map<String, Object> nameArgMap) {
StringBuilder stringBuilder = new StringBuilder();
int length = sql.length();
for (int i = 0; i < length; i++) {
char c = sql.charAt(i);
if (c == '#') {
int nextIndex = i + 1;
char nextChar = sql.charAt(nextIndex);
if (nextChar != '{') {
throw new RuntimeException(String.format("这里应该是#{\nsql:%s\nindex:%d"
, stringBuilder.toString(), nextIndex));
}
StringBuilder argSB = new StringBuilder();
i = parseSQLArg(argSB, sql, nextIndex);
String argName = argSB.toString();
Object argValue = nameArgMap.get(argName);
stringBuilder.append(argValue.toString());
continue;
}
stringBuilder.append(c);
}
return stringBuilder.toString();
}
private static int parseSQLArg(StringBuilder argSB, String sql, int nextIndex) {
nextIndex++;
for (; nextIndex < sql.length(); nextIndex++) {
char c = sql.charAt(nextIndex);
if (c != '}') {
argSB.append(c);
continue;
}
if (c == '}') {
return nextIndex;
}
}
throw new RuntimeException(String.format("缺少右括号\nindex:%d", nextIndex));
}
private static Map<String, Object> buildMethodArgNameMap(Method method, Object[] args) {
Map<String, Object> nameArgMap = Maps.newHashMap();
Parameter[] parameters = method.getParameters();
int index[] = {0};
Arrays.asList(parameters).forEach(parameter -> {
String name = parameter.getName();
nameArgMap.put(name, args[index[0]]);
index[0]++;
});
return nameArgMap;
}
}
-
架构设计
源码解析
构建SqlSessionFactory
通过XML配置文件
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputSream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
不使用XML
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
//将mapper接口添加到MapperRegistry中
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSession sqlSession = sqlSessionFactory.openSession();
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
blogMapper.selectUser();
-
使用XML配置, 是通过XPathParse来解析XML文件, 构建成一个Document, 然后如不使用XML方式将解构出来的Environment或mapper加载到Configuration对象中.
-
Environment使用了构建者模式
build过程
-
解析配置文件
SqlSessionFactoryBuilder.build()会调用XMLConfigBuilder的parse()方法, 其中调用parseConfiguration(parser.evalNode("/configuration")); 其将xml配置文件的configuration部分解析出来,
除了对其他部分进行处理外, 也调用了mapperElement(root.evalNode("mappers"));其中对
标签的子标签做了分类处理, 其中package和class都直接调用了MapperRegistry的configuration.addMapper(mapperInterface);, resource和url属性的标签都调用了mapperParser.parse();, 直接解析当前的mapper标签,最后也调用了configuration.addMapper(boundType); 注1: 如果使用代码显示调用Configuration的addMapper,那么就已经生成了对应的MappedStatement了
注2: 读取mapper时接口或者包名,那么在addMapper中会根据接口的全限定名将点换为/,并加上xml来确定mapper文件的位置, 并读取文件生成MappedStatement
-
解析mapper
parse()调用了MapperAnnotationBuilder的parse(), loadXmlResource();解析xml文件, xmlParser.parse();
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
在bindMapperForNamespace()中, 注: resource和class属性的mapper标签在之前已经调用过此方法,这里就不会再调用了
//注: 会根据namespace加载接口, boundType = Resources.classForName(namespace);
//表示已经解析了这个命名空间了
configuration.addLoadedResource("namespace:" + namespace);
//type作为key, MapperProxyFactory(存储type, (接口方法和mapperMethod作为键值对的Map,在这里并不赋值), 供getMappper生成动态代理使用)作为值
configuration.addMapper(boundType);
设置命名空间已经使用该mapper, 并将该mapper加载到configuration中. 这时mapper就与当前的type(接口)绑定了(MappedStatement).回到MapperAnnotationBuilder的parse中,
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
//对对应的mapper的xml文件进行解析
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
//对类下的方法进行解析,主要是对上面的注解进行处理
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
//未完成则再处理一次
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
//处理未完成的方法
parsePendingMethods();
}
在parseStatement中,对于当前方法进行解析
void parseStatement(Method method) {
Class<?> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
//只有使用注解的方式才会有值, 使用xml则此语句无用
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
//二级缓存在这里处理
Options options = method.getAnnotation(Options.class);
//... 其他的对注解的处理
//生成method和sql的对应关系
assistant.addMappedStatement(
mappedStatementId,
//处理后的sql语句(未填充)
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
//处理后的method
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
至此结束build方法, 设置好了Configuration
openSession
然后可以通过SqlSessionFactory的实现类DefaultSQLSEssionFactory的openSession()方法创建DefaultSqlSession
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
//默认是不自动提交事务的
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
Executor有三种模式, SIMPLE, REUSE, BATCH, 默认是SIMPLE
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
//CachingExecutor 使用装饰器模式, 对Executor进行增强
executor = new CachingExecutor(executor);
}
//pluginAll使用责任链模式, 可以通过addInterceptor或者xml文件对拦截器进行扩展, 然后在Configuration中pluginElement方法会添加到拦截器list中, 在一些结点会进行这样调用, 如这里. 这里的pluginAll就是一种责任链,会经过一层层拦截处理, 但没有对于某一层处理后就直接抛出
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
getMapper
接下来获得接口的动态代理对象
DefaultSqlSession.getMapper调用了Configuration的getMapper, 又调用了MaperRegistry.getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//是在addMapper方法, 把mapper接口作为键存起来的
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//调用jdk的reflect,生成动态代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
动态代理拦截
在继承了InvocationHandler中的MapperProxy.invoke方法中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
cachedMapperMethod方法, 先尝试从缓存中直接获取方法, 否则就重新建立一个MapperMethod(相当于维护一个map, key是method, value是sql)
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
//方法的全限定名,及sql的类型如select
this.command = new SqlCommand(config, mapperInterface, method);
// 对应的方法签名各种信息
this.method = new MethodSignature(config, mapperInterface, method);
}
SqlCommand的构造方法:
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
//MapperRestry中的addMapper就已经生成了这个mappedStatement
ms = configuration.getMappedStatement(parentStatementName);
}
execute方法中, 对于当前方法的类型, 使用switch执行不同的操作. 如select最后都是调用了selectList(), 实际上是执行了Executor的query方法 而对数据库进行更新的操作如增删改都使用update
//如果是多参数, 则返回一个map
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
DefaultSqlSession的selectList方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//绑定的sql对象, 包括已解析的带?的sql语句和对应的参数集合
BoundSql boundSql = ms.getBoundSql(parameter);
//创建一级缓存的key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
query方法中的queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//将一级缓存的key设置到localCache中
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
//将获得的key和list存入缓存
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//将之前的MappedStatement等一系列执行sql所需的数据都移交给statement执行器
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//框架中喜欢用prepare来作为某些核心操作之前的准备工作, 这个节点可以让用户进行复写
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
//每一个层级关闭一个层级的连接,这里关闭statement
closeStatement(stmt);
}
}
创建StatementHandler时, 会同步创建ParameterHandler和ResultSetHandler, 同时会通过拦截器链拦截
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
//先走这个方法来创建, 在RoutingStatementHandler根据StatementType(STATEMENT, PREPARED, CALLABLE)创建不同的StatmentHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//走拦截器链, 在mybatisPlus中, 通过向这个拦截器链添加自定义的拦截器(PaginationInteceptor), 可以实现自动分页处理
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
一个操作一个事务, 一个连接包含在一个事务里面, 一个事务唯一持有一个连接, 这里获取连接池连接和关闭连接都是由事务进行
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
//将参数填充到PreparedStatement的?中
handler.parameterize(stmt);
return stmt;
}
doQuery方法中执行了StatementHandler.query方法(以下是java.sql.PreparedStatementHandler实现的接口)
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//如果返回true,表示有ResultSet返回, 返回false表示是一个更新计算或者没有返回. 这样可以通过执行这个方法, 根据其返回值来选择调用executeUpdate 还是executeQuery
ps.execute();
//对jdbc返回的ResultSet进行处理
return resultSetHandler.<E> handleResultSets(ps);
}
ResultSet处理过程
public List<Object> handleResultSets(Statement stmt) throws SQLException {
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
//方法中从Statement中获得ResultSet对象, 并和Configuration一起封装到wrapper中
ResultSetWrapper rsw = getFirstResultSet(stmt);
//读取xml文件获得的ResultMap(注:ResultType也会被解析成ResultMap)
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
//对结果集处理解析映射到ResultMap中
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
......
handleResultSet->handleRowValues->handleRowValuesForSimpleResultMap->getRowValue->createResultObject中递归处理(日后再看), 结果集的映射, 对象的封装
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
//获得元数据
final MetaObject metaObject = configuration.newMetaObject(rowValue);
//判断对象是否使用了构造方法映射(如果没有构造方法, 则是false)
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
//在xml中使用了ResultType的情况下, 使用自动映射将结果集放到property中
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
}
//拿到解析后的ResultMap, 进行属性映射结果集
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = (foundValues || configuration.isReturnInstanceForEmptyRow()) ? rowValue : null;
}
return rowValue;
}
Sping整合
整合所需配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="省略">
<!-- 配置组件包扫描的位置 -->
<context:component-scan base-package="cn.sxt" />
<!-- 读取db.properties配置文件到Spring容器中 -->
<context:property-placeholder
location="classpath:db.properties" />
<!-- 配置 阿里巴巴的 druid 数据源(连接池) -->
<bean id="dataSource"
class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close">
<!-- SpringEL 语法 ${key} -->
<property name="driverClassName"
value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<!-- ${username}如果key是username,name 默认spring框架调用的当前操作系统的账号 解决方案:可以统一给key加一个前缀 -->
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="${jdbc.maxActive}" />
</bean>
<!-- 创建SqlSessionFactory MyBatis会话工厂对象 -->
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource" />
<!-- 读取映射文件 ,MyBatis的纯注解不用配置 -->
<property name="mapperLocations">
<array>
<!-- 配置单个映射文件 -->
<!-- <value>classpath:cn/zj/ssm/mapper/UserMapper.xml</value> -->
<!-- 配置多个映射文件使用 * 通配符 -->
<value>classpath:cn/sxt/mapper/*Mapper.xml</value>
</array>
</property>
<!-- 配置mybatis-confg.xml主配置文件(注配置文件可以保留一些个性化配置,缓存,日志,插件) -->
<property name="configLocation"
value="classpath:mybatis-config.xml" />
<!-- 配置别名,使用包扫描 -->
<property name="typeAliasesPackage" value="cn.sxt.pojo"></property>
</bean>
<!-- SqlSession 不用单独创建,每次做crud操作都需要Mapper接口的代理对象 而代理对象的创建又必须有 SqlSession对象创建
Spring在通过MyBatis创建 Mapper接口代理对象的时候,底层自动把SqlSession会话对象创建出来 -->
<!-- 创建UserMapper接口的代理对象,创建单个代理对象 参考桥梁包:org.mybatis.spring.mapper.MapperFactoryBean<T>
此类就是创建 Mapper 代理对象的类 -->
<!--<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
注入UserMapper接口 <property name="mapperInterface" value="cn.sxt.mapper.UserMapper"/>
注入sqlSessionFactory工厂对象 <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean> -->
<!-- 使用包扫描创建代理对象,包下面所有Mapper接口统一创建代理对象 使用桥梁包下面 : org.mybatis.spring.mapper.MapperScannerConfigurer
可以包扫描创建所有映射接口的代理对象 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 配置SqlSessionFactoryBean的名称 -->
<property name="basePackage" value="cn.sxt.mapper"/>
<!-- 可选,如果不写,Spring启动时候。容器中。自动会按照类型去把SqlSessionFactory对象注入进来 -->
<!-- <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- 1.配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
2.配置事务的细节
配置事务通知/增强
-->
<tx:advice id="tx" transaction-manager="transactionManager" >
<!-- 配置属性 -->
<tx:attributes>
<!-- DQL -->
<tx:method name="select*" read-only="true" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="5"/>
<tx:method name="query*" read-only="true" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="5"/>
<tx:method name="get*" read-only="true" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="5"/>
<tx:method name="find*" read-only="true" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="5"/>
<!-- 其他 -->
<tx:method name="*" read-only="false" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="5"/>
</tx:attributes>
</tx:advice>
<!--
3.使用AOP将事务切到Service层
-->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut expression="execution(* cn.zj.ssm.service..*.*(..))" id="pt"/>
<!-- 配置切面= 切入点+通知 -->
<aop:advisor advice-ref="tx" pointcut-ref="pt"/>
</aop:config>
</beans>
SqlSessionFactory的创建
org.mybatis.spring.SqlSessionFactoryBean, 实现了FactoryBean
-
FactoryBean由BeanFactory中使用的对象实现的接口,这些对象本身就是单个对象的工厂。 如果Bean实现此接口,则它将生成对象的工厂暴露在外,而不是直接暴露Bean实例。
注意:实现此接口的bean不能用作普通bean。 FactoryBean是被定义为一个bean类型,但是暴露在外的始终是getObject() 创建的对象。FactoryBeans可以支持单例和原型,并且可以按需延迟创建对象,也可以在启动时急于创建对象. 此接口是Spring框架常用的,如ProxyFactoryBean在实例化时,如果当前Bean是FactoryBean时,需要单独处理, 返回其getObject()
-
InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。在包含BeanFactory设置了所有bean属性并满足BeanFactoryAware , ApplicationContextAware等之后调用。
SqlSessionFactoryBean在spring初始化过程中建立一个单例共享的SQLSEssionFactory, 其中里面的参数没有通过依赖注入, 而是需要在配置文件中property中配置. 且在Spring调用了afterPropertiesSet方法后会回调,回调中调用了buildSqlSessionFactory()来生成SqlSessionFactory. 这个过程相当于没整合Spring前的build方法.
//Spring会通过ResourceEditor类对配置文件中的xml文件做自动转换, 转成Resource格式
private Resource[] mapperLocations;
SqlSession的创建
- 在创建好sqlSessionFactory之后,接着就要配置sqlSession的创建。
<bean id="simpleTempalte" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
<constructor-arg index="1" value="SIMPLE" />
</bean>
- 构造参数,包括sqlSessionFactory对象,以及ExecutorType(simple)
- sqlSession接口
- 我们的应用程序,是直接注入sqlSessionTemplate的(与Spring事务管理一起使用的线程安全,Spring管理的SqlSession ,可确保使用的实际SqlSession是与当前Spring事务关联的那个。 另外,它管理session生命周期,包括根据Spring事务配置根据需要关闭,提交或回滚会话。该template需要一个SqlSessionFactory来创建SqlSession,并将其作为构造函数参数传递。 也可以构造它来指示要使用的执行程序类型,否则,将使用sessionFactory中定义的默认执行程序类型。)
simpleTemplate.delete(Statement.getStatement(CxCaseMapper.class, "deleteById"), id);
- 实现类sqlSessionTemplate
public class SqlSessionTemplate implements SqlSession {
//session 工厂
private final SqlSessionFactory sqlSessionFactory;
// 对于数据库的操作类型
private final ExecutorType executorType;
//sqlSession代理
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
}
- sqlSession对每一个数据库的操作,实际上是引用代理对象sqlSessionProxy 对于目标方法的执行。
@Override
public int delete(String statement) {
return this.sqlSessionProxy.delete(statement);
}
- 在构造方法中,给代理对象已经其他属性赋予的默认值
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator; //构造代理对象
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //这里获取的sqlSession 其实是DefaultSqlSession
//getSqlSession从Spring事务管理器获取SqlSession或根据需要创建一个新的SqlSession。 尝试从当前事务中获取SqlSession。 如果没有,它将创建一个新的。 然后,如果Spring TX是活动的并且SpringManagedTransactionFactory被配置为事务管理器,它将事务同步SqlSession与事务同步
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try { // 代理对象对于目标对象的调用,其实是defaultSqlSession 对于目标方法的调用
Object result = method.invoke(sqlSession, args);
//当前不在事务时直接提交
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) { //调用目标方法后,关闭Session。
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
- defaultSqlSession 对于目标方法的执行
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
private boolean autoCommit;
private boolean dirty;
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
-
SqlSessionHolder作用
1、 sqlSessionHolder 是位于mybatis-spring 包下面,他的作用是对于sqlSession和事务的控制
- sqlSessionHolder 继承了spring的ResourceHolderSupport
public abstract class ResourceHolderSupport implements ResourceHolder { //事务是否开启 private boolean synchronizedWithTransaction = false; private boolean rollbackOnly = false; private Date deadline; // 引用次数 private int referenceCount = 0; private boolean isVoid = false; }
2 、sqlSessionTemplate 操作数据库实际操作是对于代理对象 目标方法的执行。
- 代理对象是如何获取defaultSqlSession ,在代理方法中通过SqlSessionUtils 的方法获取SqlSession
- 它会首先获取SqlSessionHolder,SqlSessionHolder用于在TransactionSynchronizationManager中保持当前的SqlSession。
- 如果holder不为空,并且holder被事务锁定,则可以通过holder.getSqlSession()方法,从当前事务中获取sqlSession,即 Fetched SqlSession from current transaction。
- 如果不存在holder或没有被事务锁定,则会创建新的sqlSession,即 Creating a new SqlSession,通过sessionFactory.openSession()方法。
- 如果当前线程的事务是活跃的,将会为SqlSession注册事务同步,即 Registering transaction synchronization for SqlSession。
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { //从从前线程的threadLocal 中获取sqlSessionHolder SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); //调用静态方法sessionHoler 判断是否存在符合要求的sqlSession SqlSession session = sessionHolder(executorType, holder); // 判断当前sqlSessionHolder 中是否持有sqlSession (即当前操作是否在事务当中) if (session != null) { //如果持有sqlSesison 的引用,则直接获取 return session; } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Creating a new SqlSession"); } //获取新的sqlSession 对象。这里由sessionFacory产生的defaultSqlSession session = sessionFactory.openSession(executorType); //判断判断,当前是否存在事务,将sqlSession 绑定到sqlSessionHolder 中,并放到threadLoacl 当中 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }
private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) { SqlSession session = null; if (holder != null && holder.isSynchronizedWithTransaction()) { //hodler保存的执行类型和获取SqlSession的执行类型不一致,就会抛出异常,也就是说在同一个事务中,执行类型不能变化,原因就是同一个事务中同一个sqlSessionFactory创建的sqlSession会被重用 if (holder.getExecutorType() != executorType) { throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction"); } //增加该holder,也就是同一事务中同一个sqlSessionFactory创建的唯一sqlSession,其引用数增加,被使用的次数增加 holder.requested(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction"); } //返回sqlSession session = holder.getSqlSession(); } return session; }
- 注册的方法如下
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) { SqlSessionHolder holder; //判断事务是否存在 if (TransactionSynchronizationManager.isSynchronizationActive()) { Environment environment = sessionFactory.getConfiguration().getEnvironment(); //加载环境变量,判断注册的事务管理器是否是SpringManagedTransaction,也就是Spring管理事务 if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]"); } holder = new SqlSessionHolder(session, executorType, exceptionTranslator); //如果当前回话处在事务当中,则将holder 绑定到ThreadLocal 中 //以sessionFactory为key,hodler为value,加入到TransactionSynchronizationManager管理的本地缓存ThreadLocal<Map<Object, Object>> resources中 TransactionSynchronizationManager.bindResource(sessionFactory, holder); //将holder, sessionFactory的同步加入本地线程缓存中ThreadLocal<Set<TransactionSynchronization>> synchronizations TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); //设置当前holder和当前事务同步 holder.setSynchronizedWithTransaction(true); //holder 引用次数+1 holder.requested(); } else { if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional"); } } else { throw new TransientDataAccessResourceException( "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active"); } } }
- 在sqlSession 关闭session 的时候, 使用了工具了sqlSessionUtils的closeSqlSession 方法。sqlSessionHolder 也是做了判断,如果回话在事务当中,则减少引用次数,没有真实关闭session。如果session不存在事务,则直接关闭session
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { notNull(session, NO_SQL_SESSION_SPECIFIED); notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); //如果holder 中持有sqlSession 的引用,(即会话存在事务) if ((holder != null) && (holder.getSqlSession() == session)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Releasing transactional SqlSession [" + session + "]"); } //每当一个sqlSession 执行完毕,则减少holder 持有引用的次数 holder.released(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Closing non transactional SqlSession [" + session + "]"); } //如果回话中,不存在事务,则直接关闭session session.close(); } }
mapper文件的创建
-
注解方式
MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口, 通过被@Import的方式可以实现手动扫描注解来手动注册Bean, 参考: https://blog.csdn.net/jiachunchun/article/details/94569246
这个类的registerBeanDefinitions方法里, 扫描mapperScan注解, 对package参数进行解析, 通过ClassPathMapperScaner的registerBeanDefinition方法进行注册BeanDefinition到spring容器中了, 同时通过复写父类的doScan()调用processBeanDefinitions()来修改definition, 将其修改成工厂bean, 然后spring实例化时, 通过factorybean生成对应的mapper文件的bean
-
xml方式
MapperScannerConfigurer类会被spring通过读取xml来创建, 其实现了BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法,会被spring回调创建mapper的BeanDefinition, 然后在doScan中修改类型(同注解)
//设置definition的beanClass为MapperFacotryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
//通过spring管理的SqlSession注入mapper
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
//MapperFactoryBean实现了FactoryBean, 其覆盖了getObject方法, 可以生产出对应的mapper实例
@Override
public T getObject() throws Exception {
// 从sqlsession中实例化真实bean
return getSqlSession().getMapper(this.mapperInterface);
}
mapper的代理对象生成过程
https://www.cnblogs.com/youzhibing/p/10486307.html
总结
statementHandler(过程处理包括了后两者)->parameterHandler(参数处理, 如果是PrepareStatment, 就对sql中的参数注入)->resultSetHandler(结果映射处理, 数据库的返回resultSet映射到对象中)
设计模式
构建者模式
在一个工厂中, 需要建造一把斧子, 斧子需要金银铜铁等一系列材料, 需要往里传入这些材料才能建造. 但是消费者可以不知道这些需求, 只要指定需要的产品, 工厂自动添加对应材料去建造, 这就是构建者模式
当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。
解决的问题
当一个类的构造函数参数超过4个,而且这些参数有些是可选的时,我们通常有两种办法来构建它的对象。 例如我们现在有如下一个类计算机类Computer
,其中cpu与ram是必填参数,而其他3个是可选参数,那么我们如何构造这个类的实例呢,通常有两种常用的方式:
public class Computer {
private String cpu;//必须
private String ram;//必须
private int usbCount;//可选
private String keyboard;//可选
private String display;//可选
}
第一:折叠构造函数模式(telescoping constructor pattern ),这个我们经常用,如下代码所示
public class Computer {
...
public Computer(String cpu, String ram) {
this(cpu, ram, 0);
}
public Computer(String cpu, String ram, int usbCount) {
this(cpu, ram, usbCount, "罗技键盘");
}
public Computer(String cpu, String ram, int usbCount, String keyboard) {
this(cpu, ram, usbCount, keyboard, "三星显示器");
}
public Computer(String cpu, String ram, int usbCount, String keyboard, String display) {
this.cpu = cpu;
this.ram = ram;
this.usbCount = usbCount;
this.keyboard = keyboard;
this.display = display;
}
}
第二种:Javabean 模式,如下所示
public class Computer {
...
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getRam() {
return ram;
}
public void setRam(String ram) {
this.ram = ram;
}
public int getUsbCount() {
return usbCount;
}
...
}
那么这两种方式有什么弊端呢?
第一种主要是使用及阅读不方便。你可以想象一下,当你要调用一个类的构造函数时,你首先要决定使用哪一个,然后里面又是一堆参数,如果这些参数的类型很多又都一样,你还要搞清楚这些参数的含义,很容易就传混了。。。那酸爽谁用谁知道。
第二种方式在构建过程中对象的状态容易发生变化,造成错误。因为那个类中的属性是分步设置的,所以就容易出错。
为了解决这两个痛点,builder模式就横空出世了。
如何实现
- 在Computer 中创建一个静态内部类 Builder,然后将Computer 中的参数都复制到Builder类中。
- 在Computer中创建一个private的构造函数,参数为Builder类型
- 在Builder中创建一个
public
的构造函数,参数为Computer中必填的那些参数,cpu 和ram。 - 在Builder中创建设置函数,对Computer中那些可选参数进行赋值,返回值为Builder类型的实例
- 在Builder中创建一个
build()
方法,在其中构建Computer的实例并返回
下面代码就是最终的样子
public class Computer {
private final String cpu;//必须
private final String ram;//必须
private final int usbCount;//可选
private final String keyboard;//可选
private final String display;//可选
private Computer(Builder builder){
this.cpu=builder.cpu;
this.ram=builder.ram;
this.usbCount=builder.usbCount;
this.keyboard=builder.keyboard;
this.display=builder.display;
}
public static class Builder{
private String cpu;//必须
private String ram;//必须
private int usbCount;//可选
private String keyboard;//可选
private String display;//可选
public Builder(String cup,String ram){
this.cpu=cup;
this.ram=ram;
}
public Builder setUsbCount(int usbCount) {
this.usbCount = usbCount;
return this;
}
public Builder setKeyboard(String keyboard) {
this.keyboard = keyboard;
return this;
}
public Builder setDisplay(String display) {
this.display = display;
return this;
}
public Computer build(){
return new Computer(this);
}
}
//省略getter方法
}
如何使用
在客户端使用链式调用,一步一步的把对象构建出来。
Computer computer=new Computer.Builder("因特尔","三星")
.setDisplay("三星24寸")
.setKeyboard("罗技")
.setUsbCount(2)
.build();
责任链模式
击鼓传花, https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html.
interface Interceptor {
Object plugin(Object target, InterceptorChain chain);
}
class InterceptorA implements Interceptor {
@Override
public Object plugin(Object target, InterceptorChain chain) {
System.out.println("InteceptorA");
return chain.plugin(target);
}
}
class InterceptorB implements Interceptor {
@Override
public Object plugin(Object target, InterceptorChain chain) {
System.out.println("InteceptorB");
return target;
}
}
class InterceptorC implements Interceptor {
@Override
public Object plugin(Object target, InterceptorChain chain) {
System.out.println("InteceptorC");
return target;
}
}
class InterceptorChain {
private List<Interceptor> interceptorList = Lists.newArrayList();
Iterator<Interceptor> iterator;
public void addInterceptor(Interceptor interceptor) {
interceptorList.add(interceptor);
}
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptorList) {
target = interceptor.plugin(target, this);
}
return target;
}
public Object plugin(Object target) {
if (iterator == null) {
iterator = interceptorList.iterator();
}
if (iterator.hasNext()) {
Interceptor next = iterator.next();
next.plugin(target, this);
}
return target;
}
}
public class InterceptorDemo {
public static void main(String[] args) {
InterceptorA interceptorA = new InterceptorA();
InterceptorB interceptorB = new InterceptorB();
InterceptorC interceptorC = new InterceptorC();
InterceptorChain interceptorChain = new InterceptorChain();
interceptorChain.addInterceptor(interceptorA);
interceptorChain.addInterceptor(interceptorB);
interceptorChain.addInterceptor(interceptorC);
interceptorChain.plugin(new Object());
}
}
模板模式
BaseSqlSession使用了模板模式, 对于有确定的方法, 写成抽象类, 确定的方法写死, 不确定的方法写成抽象方法. 如都是不确定的方法, 则写成接口
多线程优化Mybatis
背景
- Mybatis在构建SqlSessionFactory时要读取所有的Mapper, 加载到Configuration中, 这里如果使用多线程, 或许可以优化性能
- 在多线程demo中, 并行流竞争过大,膨胀的重量级锁切换消耗时间过大. 在阈值1 << 13 (2的13次方)时, 并行流的性能优势开始显现. 可以通过判断mapper总数, 来使用不同的读取方式改进.
锁分类
- 偏向锁:无实际竞争,且将来只有第一个申请锁的线程会使用锁。如有竞争膨胀为轻量级锁
- 轻量级锁(如CAS锁):无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。如超过竞争限制膨胀为重量级锁
- 重量级锁:有实际竞争,且锁竞争时间长。
- 以上都是内置锁, 是JVM对Synchrogazed做的优化
自旋锁
public calss NickleLock {
private static final Unsafe unsafe;
private static final long valueOffset;
static {
try {
Class<Unsafe> unsafeClass = Unsafe.calss;
Filed theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe)theUnsafe.get(null);
valueOffset = unsafe.objectFiledOffset
(NickleLocl.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex);
}
}
private volatile int value;
public void lock() {
for (;;) {
if(unsafe.compareAndSwapInt(this, valueOffset, 0 , 1)){
return;
}
//当获取锁失败时,线程让步, 不一直等待, 这一部分可以考虑使用AQS抽象队列同步器进行优化.
Thread.currentThread().yield();
}
}
public void unlock() {
value = 0;
}
}
优化代码
public class MybatisEnhance {
private static Map<Object, Object> objectMap = new HashMap<>(size);
private static NickleLock nickleLock = new NickleLock();
@Data
public class MapperScanner{
private String mapperLocation;
public List<Object> scanMapper(){
List<Object> objects = Lists.newArrayList();
for (int i = 0; i < size; i++){
objects.add(new Object());
}
return objects;
}
}
public static void put(Objects obj){
nickleLock.lock();
obectMap.put(obj, obj);
nickleLock.unlock();
}
}