1, What is deferred loading
In the development process, we do not always need to load the user's order information when loading the user's information. This is what we call delayed loading.
1. Definition of delayed loading
Delayed loading is to load when data is needed, and not when data is not needed. Lazy loading is also called lazy loading.
2. Advantages and disadvantages
1) Advantages:
Query from a single table first, and then associate the query from the associated table when necessary, which greatly improves the database performance, because querying a single table is faster than associating multiple tables.
2) Disadvantages:
Because the database query is only performed when the data is needed. In this way, in the case of mass data query, the query also consumes time, which may cause the user waiting time to change and the user experience to decline.
3. When will it be used
In multiple tables:
1) One to many, many to many: delay loading is usually adopted;
2) One to one (many to one): immediate loading is usually used.
4. Note: deferred loading is implemented based on nested queries.
2, How
1. Local delayed loading
There is a fetchType attribute in the association and collection tags. You can modify the local loading policy by modifying its value.
<!-- Enable one to many delayed loading --> <resultMap id="userMap" type="user"> <id column="id" property="id"></id> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="birthday" property="birthday"></result> <!-- fetchType="lazy" Lazy loading strategy fetchType="eager" Load policy now --> <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>
2. Global delayed loading
In the core configuration file of Mybatis, you can use the setting tag to modify the global loading policy.
<settings> <!-- Enable global delayed loading function --> <setting name="lazyLoadingEnabled" value="true"/> </settings>
be careful:
The local loading policy takes precedence over the global loading policy.
3. Set the method to trigger delayed loading
After configuring the delayed loading strategy, we found that even if no method of the associated object is called, the query of the associated object will be triggered when you call the equals, clone, hashCode and toString methods of the current object.
We can override the above four methods in the configuration file by using the lazyloadtrigger methods configuration item.
<settings> <!-- Enable global delayed loading function --> <setting name="lazyLoadingEnabled" value="true"/> <!-- All methods delay loading --> <setting name="lazyLoadTriggerMethods" value=""/> </settings>
3, Delayed loading principle
1. Understanding description
The principle is actually very simple, that is, when analyzing the User's member variables, if there is a lazy loading configuration, such as fetchType = "lazy", the User will be converted into a proxy class and returned. And put the lazy load related objects into the ResultLoaderMap and save them. When the get method in the corresponding User object is called to obtain lazy load variables, sql acquisition is performed according to the proxy class.
Summary: delayed loading is mainly realized in the form of dynamic agent, which intercepts the specified method and performs data loading.
MyBatis delayed loading is mainly implemented by Javassist and Cglib. The class diagram shows:
2. Related code classes
Related properties in Configuration class:
public class Configuration { /** * aggressiveLazyLoading: * When on, any method call will load all the properties of the object. * Otherwise, each attribute is loaded on demand (see lazyLoadTriggerMethods). * The default is true */ protected boolean aggressiveLazyLoading; // Delayed loading trigger method protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString")); // Enable deferred loading protected boolean lazyLoadingEnabled = false; /** * Javassist proxy factory is used by default * @param proxyFactory */ public void setProxyFactory(ProxyFactory proxyFactory) { if (proxyFactory == null) { proxyFactory = new JavassistProxyFactory(); } this.proxyFactory = proxyFactory; } // Omit.. }
4, View analysis source code
1,DefaultResultSetHandler
The query results of Mybatis are processed by the handleResultSets() method of the ResultSetHandler interface. There is only one implementation of the ResultSetHandler interface, DefaultResultSetHandler. Next, let's look at a core method related to delayed loading.
/** * Create result object * Tag call path: (all in DefaultResultSetHandler class) * handleResultSets -> handleResultSet -> handleRowValues -> * handleRowValuesForNestedResultMap -> getRowValue -> createResultObject */ private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { // reset previous mapping result this.useConstructorMappings = false; final List<Class<?>> constructorArgTypes = new ArrayList<>(); final List<Object> constructorArgs = new ArrayList<>(); // Create the real object of the returned result map 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 // Judge whether the attribute is configured with nested query. If so, create a proxy object if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { // Create a deferred load proxy object resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } // set current mapping result this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); return resultObject; }
The default proxyFactory implementation class in configuration is JavassistProxyFactory, that is, javassistProxy is used to create proxy objects by default.
protected ProxyFactory proxyFactory = new JavassistProxyFactory();
2. JavasisstProxyFactory implementation
public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory { /** * Interface implementation * @param target Target result object * @param lazyLoader Delay loading objects * @param configuration to configure * @param objectFactory Object factory * @param constructorArgTypes Construction parameter type * @param constructorArgs Construction parameter value * @return */ @Override public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } // Omit.. // Proxy object implementation, core logic execution private static class EnhancedResultObjectProxyImpl implements MethodHandler { 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; } static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { ProxyFactory enhancer = new ProxyFactory(); enhancer.setSuperclass(type); try { // Determine whether the method exists by obtaining the object method type.getDeclaredMethod(WRITE_REPLACE_METHOD); // ObjectOutputStream will call writeReplace of objects returned by writeReplace if (LogHolder.log.isDebugEnabled()) { LogHolder.log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this"); } } catch (NoSuchMethodException e) { // The method is not found, implement the interface enhancer.setInterfaces(new Class[] { WriteReplaceInterface.class }); } catch (SecurityException e) { // nothing to do here } Object enhanced; Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]); Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]); try { // Create a new proxy object enhanced = enhancer.create(typesArray, valuesArray); } catch (Exception e) { throw new ExecutorException("Error creating lazy proxy. Cause: " + e, e); } // Set up agent executor ((Proxy) enhanced).setHandler(callback); return enhanced; } /** * Proxy object execution * @param enhanced Original object * @param method Original object method * @param methodProxy Proxy method * @param args Method parameters * @return * @throws Throwable */ @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)) { // Ignore, no specific role found 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 { // The number of delayed loads is greater than 0 if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { // aggressive one-time loadability requires all deferred load attributes or contains methods that trigger deferred load if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { // Load all at once lazyLoader.loadAll(); } else if (PropertyNamer.isSetter(methodName)) { // Judge whether it is a set method. The set method does not need to delay loading final String property = PropertyNamer.methodToProperty(methodName); lazyLoader.remove(property); } else if (PropertyNamer.isGetter(methodName)) { final String property = PropertyNamer.methodToProperty(methodName); if (lazyLoader.hasLoader(property)) { // Delay loading a single attribute lazyLoader.load(property); } } } } } return methodProxy.invoke(enhanced, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } } }
5, Precautions
1. IDEA debugging problem
When aggressiveLazyLoading = true is configured, when using IDEA for debugging, if the breakpoint hits the agent execution logic, you will find that the code loaded late can never enter and will always be executed in advance. The main reason is aggressiveLazyLoading, because the method of delaying loading objects has been triggered in the debugger form of IDEA during debugging.
Article content output source: pull hook education Java high salary training camp;