+ 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;
+ }
+}
+
+具体实现
+https://github.com/wangwangit/mybatis-study.git
+
+重点剖析
架构设计
+主要组件
+
+
+组件 |
+描述 |
+
+
+
+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);
+ }
+ }
+}
+
+
+