JDBC问题总结
* 数据库连接创建,释放频繁,浪费系统资源
* SQL语句硬编码
* SQL参数设置存在硬编码
* 结果解析存在硬编码
框架设计思路
使⽤端:
提供核⼼配置⽂件:
- sqlMapConfig.xml : 存放数据源信息,引⼊mapper.xml
<!--提取配置到文件中,解决硬编码问题-->
<configuration>
<!--数据库连接信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///mybatis"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
<!--引⼊sql配置信息-->
<mapper resource="mapper.xml"></mapper>
</configuration>
- Mapper.xml : sql语句的配置⽂件信息
<mapper namespace="com.ww.mapper.IUserDao">
<!--sql的唯一标识: namespace.id-->
<select id="findAll" resultType="com.ww.pojo.User">
select * form user
</select>
<select id="findByCondition" resultType="com.ww.pojo.User" paramterType="com.ww.pojo.User">
select * from user where id = #{id} and username = #{username}
</select>
</mapper>
框架端:
1.读取配置⽂件,封装到实体配置类中
- Configuration: 数据库连接配置
/*定义基于sqlMapConfig.xml的配置类*/
@Data
public class Configuration {
/*数据库连接配置*/
private DataSource dataSource;
/**
* 封装mapper.xml中的SQL语句
* key: statementId namespace.sqlId
*/
private Map<String, MappedStatement> mapppedStatementMap = new HashMap<>();
}
- MappedStatement: SQL语句配置
/**
* 定义一个SQL该有的属性, 唯一标识,出参入参,以及SQL语句
*/
@Data
public class MappedStatement {
// id标识
private String id;
//返回值类型
private String resultType;
//参数类型
private String paramterType;
//sql语句
private String sql;
}
2.解析配置⽂件
- 加载配置文件
InputStream resource = Resources.class.getClassLoader().getResourceAsStream(path);
- 使用dom4j解析配置封装到Configuration
public Configuration parseConfig(InputStream inputStream)throws Exception{
Document document=new SAXReader().read(inputStream);
// <configuration>
Element rootElement=document.getRootElement();
List<Element> list=rootElement.selectNodes("//property");
// 解析配置文件
Properties properties=new Properties();
for(Element element:list){
String name=element.attributeValue("name");
String value=element.attributeValue("value");
properties.setProperty(name,value);
}
// 获取连接池
ComboPooledDataSource comboPooledDataSource=new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
comboPooledDataSource.setUser(properties.getProperty("username"));
comboPooledDataSource.setPassword(properties.getProperty("password"));
configuration.setDataSource(comboPooledDataSource);
// 解析mapper文件
List<Element> list1=rootElement.selectNodes("//mapper");
for(Element element:list1){
String mapperPath=element.attributeValue("resource");
InputStream resource=Resources.getResourceAsStream(mapperPath);
XMLMapperBuilder xmlMapperBuilder=new XMLMapperBuilder(configuration);
xmlMapperBuilder.parse(resource);
}
return configuration;
3.通过SqlSessionFactoryBuilder的build方法,创建SqlSessionFactory
/* 定义工厂,创建会话使用 */
public interface SqlSessionFactory {
public SqlSession openSession();
}
4.通过SqlSessionFactory的openSession方法,创建SqlSession
5.在sqlSession中封装增删改查方法
- 定义方法
public interface SqlSession {
//查询所有
public <E> List<E> selectList(String statementId, Object... params) throws Exception;
//根据条件查询单个
public <T> T selectOne(String statementId, Object... params) throws Exception;
//为Dao层生成代理实现类
public <T> T getMapper(Class<?> mapperClass);
}
- 动态代理实现
public<T> T getMapper(Class<?> mapperClass){
//使用JDK动态代理,来为dao接口生成代理对象并返回
Object proxyInstance=Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(),new Class[]{mapperClass},new InvocationHandler(){
@Override
public Object invoke(Object proxy,Method method,Object[]args)throws Throwable{
String methodName=method.getName();
String className=method.getDeclaringClass().getName();
String statementId=className+"."+methodName;
//获取方法的返回值类型
Type genericReturnType=method.getGenericReturnType();
if(genericReturnType instanceof ParameterizedType){
List<Object> objects=selectList(statementId,args);
return objects;
}
return selectOne(statementId,args);
}
});
return(T)proxyInstance;
}
6.定义Executor实现JDBC原生操作,以及出入参映射操作
public class SimpleExecutor implements Executor {
@Override
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
//1.注册驱动,获取链接
Connection connection = configuration.getDataSource().getConnection();
//2.获取sql语句
String sql = mappedStatement.getSql();
//3.占位符替换
BoundSql boundSql = getBound(sql);
//4.获取预处理对象
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
//5.设置参数
//获取参数全路径
String paramterType = mappedStatement.getParamterType();
Class<?> paramterTypeClass = getClassType(paramterType);
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
String content = parameterMapping.getContent();
//反射
Field declaredField = paramterTypeClass.getDeclaredField(content);
//暴力访问
declaredField.setAccessible(true);
Object o = declaredField.get(params[0]);
preparedStatement.setObject(i + 1, o);
}
//6.执行sql
ResultSet resultSet = preparedStatement.executeQuery();
String resultType = mappedStatement.getResultType();
Class<?> resultTypeClass = getClassType(resultType);
List<Object> list = new ArrayList<>();
//7.返回结果集
while (resultSet.next()) {
Object o = resultTypeClass.newInstance();
//元数据
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
//字段名
String columnName = metaData.getColumnName(i);
//字段的值
Object value = resultSet.getObject(columnName);
//使用反射,根据数据库表和实体的对应关系,进行映射
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o, value);
}
list.add(o);
}
return (List<E>) list;
}
private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
if (null != paramterType) {
Class<?> aClass = Class.forName(paramterType);
return aClass;
}
return null;
}
/**
* 将#{}使用?替换
* 解析出#{}里面的参数值
*
* @param sql
* @return
*/
private BoundSql getBound(String sql) {
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
//解析SQL
String parseSql = genericTokenParser.parse(sql);
//#{}解析出来的参数
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
return boundSql;
}
}
具体实现
重点剖析
架构设计
主要组件
组件 | 描述 |
---|---|
SqlSession | 表示与数据库交互的会话,完成必要数据库增删改查功能 |
Executor | MyBatis的执行器,负责SQL语句的生成和查询缓存的维护 |
StatementHandler | 封装了JDBC Statement操作,负责对JDBC Statement操作,如设置参数 |
ParameterHandler | 参数处理 |
ResultSetHandler | 结果集处理 |
TypeHandler | java类型与jdbc类型的转换 |
MappedStatement | 维护mapper.xml中 一条select,update,insert,delete节点的封装 |
SqlSource | 根据用户传递的parameterObject动态的生成SQL并封装到BoundSql中 |
BoundSql | 表示动态生成的SQL语句,以及相应的参数信息 |
一.缓存使用
1.二级缓存开启三步走
- 开启全局二级缓存配置
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 在需要使用二级缓存的mapper文件中添加配置标签
<cache></cache>
- 在CRUD标签上配置userCache=true
<select id="findById" resultType="com.lagou.pojo.User" useCache="true">
select * from user where id = #{id}
</select>
2.config中的配置解析,将配置设置到configuration中
private void parseConfiguration(XNode root){
try{
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings=settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 缓存标签解析
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
}catch(Exception e){
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+e,e);
}
}
3.mapper中的cache标签解析,以nameSpace为key存储cache到configuration中
private void cacheElement(XNode context){
if(context!=null){
String type=context.getStringAttribute("type","PERPETUAL");
Class<?extends Cache> typeClass=typeAliasRegistry.resolveAlias(type);
String eviction=context.getStringAttribute("eviction","LRU");
Class<?extends Cache> evictionClass=typeAliasRegistry.resolveAlias(eviction);
Long flushInterval=context.getLongAttribute("flushInterval");
Integer size=context.getIntAttribute("size");
boolean readWrite=!context.getBooleanAttribute("readOnly",false);
boolean blocking=context.getBooleanAttribute("blocking",false);
Properties props=context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass,evictionClass,flushInterval,size,readWrite,blocking,props);
}
}
public Cache useNewCache(Class<?extends Cache> typeClass,
Class<?extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props){
Cache cache=new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass,PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass,LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
//只会解析赋值一次
configuration.addCache(cache);
//每个mapper只有一个cache
currentCache=cache;
return cache;
}
4.每个sql语句中的userCache配置,在buildStatementFromContext时设置给每个MappedStatement对象
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets){
if(unresolvedCacheRef){
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id=applyCurrentNamespace(id,false);
boolean isSelect=sqlCommandType==SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder=new MappedStatement.Builder(configuration,id,sqlSource,sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap,resultType,id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache,!isSelect))
.useCache(valueOrDefault(useCache,isSelect))
//添加前面创建的cache对象
.cache(currentCache);
ParameterMap statementParameterMap=getStatementParameterMap(parameterMap,parameterType,id);
if(statementParameterMap!=null){
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement=statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
5.执行查询语句时,逻辑如下
public<E> List<E> query(MappedStatement ms,Object parameterObject,RowBounds rowBounds,ResultHandler resultHandler)throws SQLException{
BoundSql boundSql=ms.getBoundSql(parameterObject);
//创建缓存key
CacheKey key=this.createCacheKey(ms,parameterObject,rowBounds,boundSql);
return this.query(ms,parameterObject,rowBounds,resultHandler,key,boundSql);
}
public<E> List<E> query(MappedStatement ms,Object parameterObject,RowBounds rowBounds,ResultHandler resultHandler,CacheKey key,BoundSql boundSql)throws SQLException{
//判断是否配置了开启缓存
Cache cache=ms.getCache();
if(cache!=null){
//如果需要刷新缓存的化,flushCache="true"
this.flushCacheIfRequired(ms);
if(ms.isUseCache()&&resultHandler==null){
this.ensureNoOutParams(ms,boundSql);
//访问二级缓存
List<E> list=(List)this.tcm.getObject(cache,key);
if(list==null){
// 缓存未命中,访问一级缓存,一级缓存没有,再查询数据库
list=this.delegate.query(ms,parameterObject,rowBounds,resultHandler,key,boundSql);
// 缓存查询结果
this.tcm.putObject(cache,key,list);
}
return list;
}
}
return this.delegate.query(ms,parameterObject,rowBounds,resultHandler,key,boundSql);
}
6.二级缓存安全问题,以及为什么需要提交才能更新缓存值
// 事务缓存管理器
public class TransactionalCacheManager {
// Cache与TransactionCache的映射关系表
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
// 从映射表中获取 TransactionalCach,若不存在,则创建一个新的对象,并设cache值进去
return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
}
}
// 这是Cache缓存的一个子类
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
//真正的缓存对象,和上⾯的Map<Cache, TransactionalCache>中的Cache是同⼀个
private final Cache delegate;
private boolean clearOnCommit;
// 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中
private final Map<Object, Object> entriesToAddOnCommit;
// 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中
private final Set<Object> entriesMissedInCache;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<>();
this.entriesMissedInCache = new HashSet<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public Object getObject(Object key) {
// issue #116
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
@Override
public Object removeObject(Object key) {
return null;
}
@Override
public void clear() {
clearOnCommit = true;
entriesToAddOnCommit.clear();
}
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
//当commit时,会调用刷新方法
flushPendingEntries();
reset();
}
public void rollback() {
unlockMissedEntries();
reset();
}
private void reset() {
clearOnCommit = false;
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
//将缓存中的值刷新到真实的Cache中,
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
private void unlockMissedEntries() {
for (Object entry : entriesMissedInCache) {
try {
delegate.removeObject(entry);
} catch (Exception e) {
log.warn("Unexpected exception while notifying a rollback to the cache adapter. "
+ "Consider upgrading your cache adapter to the latest version. Cause: " + e);
}
}
}
}
二.插件开发
1.MyBatis允许拦截的方法
- 执⾏器Executor (update、query、commit、rollback等⽅法);
- SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等⽅ 法);
- 参数处理器ParameterHandler (getParameterObject、setParameters⽅法);
- 结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等⽅法);
2.这四大对象创建时,都是调用interceptorChain.pluginAll(parameterHandler)返回的代理对象
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 Object pluginAll(Object target){
for(Interceptor interceptor:interceptors){
target=interceptor.plugin(target);
}
return target;
}
default Object plugin(Object target){
return Plugin.wrap(target,this);
}
public static Object wrap(Object target,Interceptor interceptor){
Map<Class<?>,Set<Method>>signatureMap=getSignatureMap(interceptor);
Class<?> type=target.getClass();
Class<?>[]interfaces=getAllInterfaces(type,signatureMap);
if(interfaces.length>0){
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target,interceptor,signatureMap));
}
return target;
}
3.因此调用这四大对象的方法时,会执行Plugin类中的invoke()方法逻辑
@Override
public Object invoke(Object proxy,Method method,Object[]args)throws Throwable{
try{
Set<Method> methods=signatureMap.get(method.getDeclaringClass());
if(methods!=null&&methods.contains(method)){
return interceptor.intercept(new Invocation(target,method,args));
}
return method.invoke(target,args);
}catch(Exception e){
throw ExceptionUtil.unwrapThrowable(e);
}
}
4.上述处理逻辑中当方法匹配时,调用了intercept方法,所以会执行拦截器中的方法进行增强
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class ExamplePlugin implements Interceptor {
/**
* 只要被拦截的目标对象方法被执行时,每次都会执行
*
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("对方法进行了增强");
//执行原方法
return invocation.proceed();
}
/**
* 将当前拦截器存储到拦截器链中
*
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
/**
* 获取配置文件的参数
*
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("获取的配置文件参数是" + properties);
Interceptor.super.setProperties(properties);
}
}
三.动态代理
//获取mapper
userMapper=sqlSession.getMapper(IUserMapper.class);
//DefaultSqlSession
public<T> T getMapper(Class<T> type){
return this.configuration.getMapper(type,this);
}
//Configuration
public<T> T getMapper(Class<T> type,SqlSession sqlSession){
return mapperRegistry.getMapper(type,sqlSession);
}
//MapperRegistry
public<T> T getMapper(Class<T> type,SqlSession sqlSession){
final MapperProxyFactory<T> mapperProxyFactory=(MapperProxyFactory<T>)knownMappers.get(type);
if(mapperProxyFactory==null){
throw new BindingException("Type "+type+" is not known to the MapperRegistry.");
}
try{
//通过静态工厂生成示例
return mapperProxyFactory.newInstance(sqlSession);
}catch(Exception e){
throw new BindingException("Error getting mapper instance. Cause: "+e,e);
}
}
//MapperProxyFactory
public T newInstance(SqlSession sqlSession){
//创建了JDK动态代理的handler类
final MapperProxy<T> mapperProxy=new MapperProxy<>(sqlSession,mapperInterface,methodCache);
//调用重载方法
return newInstance(mapperProxy);
}
// 重载方法
protected T newInstance(MapperProxy<T> mapperProxy){
return(T)Proxy.newProxyInstance(mapperInterface.getClassLoader(),new Class[]{mapperInterface},mapperProxy);
}
//MapperProxy 类,实现了 InvocationHandler 接⼝
public class MapperProxy<T> implements InvocationHandler, Serializable {
//省略部分源码
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
//构造,传⼊了 SqlSession,说明每个session中的代理对象的不同的!
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface,
Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
//省略部分源码
}
//invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
try {
//如果是Object定义的⽅法,直接调⽤
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);
}
// 获得 MapperMethod 对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
//重点在这:MapperMethod最终调⽤了执⾏的⽅法
return mapperMethod.execute(sqlSession, args);
}
//execute方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//判断mapper中的⽅法类型,最终调⽤的还是SqlSession中的⽅法 switch
switch (command.getType()) {
case INSERT: {
//转换参数
Object param = method.convertArgsToSqlCommandParam(args);
//执⾏INSERT操作
// 转换 rowCount
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
//⽆返回,并且有ResultHandler⽅法参数,则将查询的结果,提交给 ResultHandler 进⾏处理
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
//返回结果为null,并且返回类型为基本类型,则抛出BindingException异常
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
}
四.延迟加载
1.开启全局延迟加载配置
<settings>
<!--开启全局延迟加载功能-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
2.配置局部延迟加载
<!-- 开启⼀对多 延迟加载 -->
<resultMap id="userMap" type="user">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
全局延迟加载
在Mybatis的核⼼配置⽂件中可以使⽤setting标签修改全局的加载策略。
注意
7.。
<result column="birthday" property="birthday"></result>
<!--
fetchType="lazy" 懒加载策略
fetchType="eager" ⽴即加载策略
-->
<collection property="orderList" ofType="order" column="id"
select="com.lagou.dao.OrderMapper.findByUid" fetchType="lazy">
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
SELECT * FROM `user`
</select>
3.Configuration中的配置
public class Configuration {
/** aggressiveLazyLoading:
* 当开启时,任何⽅法的调⽤都会加载该对象的所有属性。否则,每个属性会按需加载(参考
lazyLoadTriggerMethods).
* 默认为true
* */
protected boolean aggressiveLazyLoading;
/**
* 延迟加载触发⽅法
*/
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>
(Arrays.asList(new String[]{"equals", "clone", "hashCode", "toString"}));
/** 是否开启延迟加载 */
protected boolean lazyLoadingEnabled = false;
/**
延迟加载代理对象创建
Mybatis的查询结果是由ResultSetHandler接⼝的handleResultSets()⽅法处理的。ResultSetHandler
接⼝只有⼀个实现,DefaultResultSetHandler,接下来看下延迟加载相关的⼀个核⼼的⽅法
* 默认使⽤Javassist代理⼯⼚
* @param proxyFactory
*/
public void setProxyFactory(ProxyFactory proxyFactory) {
if (proxyFactory == null) {
proxyFactory = new JavassistProxyFactory();
}
this.proxyFactory = proxyFactory;
}
//省略...
}
4.查看ResultSetHandler如何实现延迟加载
private Object createResultObject(ResultSetWrapper rsw,ResultMap resultMap,ResultLoaderMap lazyLoader,String columnPrefix)throws SQLException{
this.useConstructorMappings=false; // reset previous mapping result
final List<Class<?>>constructorArgTypes=new ArrayList<>();
final List<Object> constructorArgs=new ArrayList<>();
//创建返回的结果映射的真实对
Object resultObject=createResultObject(rsw,resultMap,constructorArgTypes,constructorArgs,columnPrefix);
if(resultObject!=null&&!hasTypeHandlerForResultObject(rsw,resultMap.getType())){
final List<ResultMapping> propertyMappings=resultMap.getPropertyResultMappings();
for(ResultMapping propertyMapping:propertyMappings){
// issue gcode #109 && issue #149
// 判断属性有没配置嵌套查询,如果有就创建代理对象
if(propertyMapping.getNestedQueryId()!=null&&propertyMapping.isLazy()){
//#mark 创建延迟加载代理对象
resultObject=configuration.getProxyFactory().createProxy(resultObject,lazyLoader,configuration,objectFactory,constructorArgTypes,constructorArgs);
break;
}
}
}
this.useConstructorMappings=resultObject!=null&&!constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
5.代理对象执行逻辑
private static class EnhancedResultObjectProxyImpl implements MethodHandler {
private final Class<?> type;
private final ResultLoaderMap lazyLoader;
private final boolean aggressive;
private final Set<String> lazyLoadTriggerMethods;
private final ObjectFactory objectFactory;
private final List<Class<?>> constructorArgTypes;
private final List<Object> constructorArgs;
private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
this.type = type;
this.lazyLoader = lazyLoader;
this.aggressive = configuration.isAggressiveLazyLoading();
this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();
this.objectFactory = objectFactory;
this.constructorArgTypes = constructorArgTypes;
this.constructorArgs = constructorArgs;
}
public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
final Class<?> type = target.getClass();
EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
PropertyCopier.copyBeanProperties(type, target, enhanced);
return enhanced;
}
@Override
public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
final String methodName = method.getName();
try {
synchronized (lazyLoader) {
if (WRITE_REPLACE_METHOD.equals(methodName)) {
Object original;
if (constructorArgTypes.isEmpty()) {
original = objectFactory.create(type);
} else {
original = objectFactory.create(type, constructorArgTypes, constructorArgs);
}
PropertyCopier.copyBeanProperties(type, enhanced, original);
if (lazyLoader.size() > 0) {
return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
} else {
return original;
}
} else {
if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
lazyLoader.loadAll();
} else if (PropertyNamer.isSetter(methodName)) {
final String property = PropertyNamer.methodToProperty(methodName);
lazyLoader.remove(property);
} else if (PropertyNamer.isGetter(methodName)) {
final String property = PropertyNamer.methodToProperty(methodName);
if (lazyLoader.hasLoader(property)) {
lazyLoader.load(property);
}
}
}
}
}
return methodProxy.invoke(enhanced, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}