refactor: performance optimization

This commit is contained in:
Michael Yang 2024-05-22 17:40:03 +08:00
parent adb43c6c47
commit eb663b3518
7 changed files with 406 additions and 49 deletions

View File

@ -279,7 +279,7 @@ public class GeneratorTest {
generator.generate();
}
@Test
// @Test
public void testCodeGen5() {
// 配置数据源
HikariDataSource dataSource = new HikariDataSource();

View File

@ -22,12 +22,15 @@ import com.mybatisflex.core.keygen.MultiEntityKeyGenerator;
import com.mybatisflex.core.keygen.MultiRowKeyGenerator;
import com.mybatisflex.core.keygen.MybatisKeyGeneratorUtil;
import com.mybatisflex.core.keygen.RowKeyGenerator;
import com.mybatisflex.core.mybatis.binding.FlexMapperRegistry;
import com.mybatisflex.core.mybatis.executor.FlexBatchExecutor;
import com.mybatisflex.core.mybatis.executor.FlexReuseExecutor;
import com.mybatisflex.core.mybatis.executor.FlexSimpleExecutor;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.table.TableInfoFactory;
import com.mybatisflex.core.util.MapUtil;
import com.mybatisflex.core.util.StringUtil;
import org.apache.ibatis.binding.MapperRegistry;
import org.apache.ibatis.executor.CachingExecutor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.KeyGenerator;
@ -42,10 +45,8 @@ import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.session.*;
import org.apache.ibatis.transaction.Transaction;
import com.mybatisflex.core.util.MapUtil;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
@ -59,6 +60,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class FlexConfiguration extends Configuration {
private static final Map<String, MappedStatement> dynamicMappedStatementCache = new ConcurrentHashMap<>();
private final MapperRegistry mapperRegistry = new FlexMapperRegistry(this);
public FlexConfiguration() {
setObjectWrapperFactory(new FlexWrapperFactory());
@ -345,17 +347,35 @@ public class FlexConfiguration extends Configuration {
//不支持泛型类添加
if (!isGenericInterface) {
super.addMapper(type);
mapperRegistry.addMapper(type);
}
}
@SuppressWarnings("unchecked")
@Override
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
T mapper = super.getMapper(type, sqlSession);
return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}
, new MapperInvocationHandler(mapper, environment.getDataSource()));
// @SuppressWarnings("unchecked")
// @Override
// public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// T mapper = super.getMapper(type, sqlSession);
// return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}
// , new MapperInvocationHandler(mapper, environment.getDataSource()));
// }
public void addMappers(String packageName, Class<?> superType) {
mapperRegistry.addMappers(packageName, superType);
}
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
public boolean hasMapper(Class<?> type) {
return mapperRegistry.hasMapper(type);
}
}

View File

@ -20,7 +20,7 @@ public class MappedStatementTypes {
private MappedStatementTypes() {
}
private static ThreadLocal<Class<?>> currentTypeTL = new ThreadLocal<>();
private static final ThreadLocal<Class<?>> currentTypeTL = new ThreadLocal<>();
public static void setCurrentType(Class<?> type) {
currentTypeTL.set(type);

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.core.mybatis;
package com.mybatisflex.core.mybatis.binding;
import com.mybatisflex.annotation.UseDataSource;
import com.mybatisflex.core.FlexGlobalConfig;
@ -21,38 +21,38 @@ import com.mybatisflex.core.datasource.DataSourceKey;
import com.mybatisflex.core.datasource.FlexDataSource;
import com.mybatisflex.core.dialect.DbType;
import com.mybatisflex.core.dialect.DialectFactory;
import com.mybatisflex.core.mybatis.FlexConfiguration;
import com.mybatisflex.core.row.RowMapper;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.table.TableInfoFactory;
import com.mybatisflex.core.util.MapUtil;
import com.mybatisflex.core.util.StringUtil;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;
import javax.sql.DataSource;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author michael
* @author norkts
*/
public class MapperInvocationHandler implements InvocationHandler {
public class FlexMapperProxy<T> extends MybatisMapperProxy<T> {
private static final String NULL_KEY = "@NK";
private static final Map<Method, String> methodDsKeyCache = new ConcurrentHashMap<>();
private final Object mapper;
private final FlexDataSource dataSource;
public MapperInvocationHandler(Object mapper, DataSource dataSource) {
this.mapper = mapper;
if (dataSource instanceof FlexDataSource) {
this.dataSource = (FlexDataSource) dataSource;
} else {
this.dataSource = null;
}
public FlexMapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache,
FlexConfiguration configuration) {
super(sqlSession, mapperInterface, methodCache);
this.dataSource = (FlexDataSource) configuration.getEnvironment().getDataSource();
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
boolean needClearDsKey = false;
boolean needClearDbType = false;
try {
@ -100,7 +100,8 @@ public class MapperInvocationHandler implements InvocationHandler {
DialectFactory.setHintDbType(dbType);
needClearDbType = true;
}
return method.invoke(mapper, args);
// return method.invoke(mapper, args);
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
} catch (Throwable e) {
throw ExceptionUtil.unwrapThrowable(e);
} finally {
@ -115,30 +116,33 @@ public class MapperInvocationHandler implements InvocationHandler {
private static String getConfigDataSourceKey(Method method, Object proxy) {
UseDataSource useDataSource = method.getAnnotation(UseDataSource.class);
if (useDataSource != null && StringUtil.isNotBlank(useDataSource.value())) {
return useDataSource.value();
}
Class<?>[] interfaces = proxy.getClass().getInterfaces();
for (Class<?> anInterface : interfaces) {
UseDataSource annotation = anInterface.getAnnotation(UseDataSource.class);
if (annotation != null) {
return annotation.value();
String result = MapUtil.computeIfAbsent(methodDsKeyCache, method, method1 -> {
UseDataSource useDataSource = method1.getAnnotation(UseDataSource.class);
if (useDataSource != null && StringUtil.isNotBlank(useDataSource.value())) {
return useDataSource.value();
}
}
if (interfaces[0] != RowMapper.class) {
TableInfo tableInfo = TableInfoFactory.ofMapperClass(interfaces[0]);
if (tableInfo != null) {
String dataSourceKey = tableInfo.getDataSource();
if (StringUtil.isNotBlank(dataSourceKey)) {
return dataSourceKey;
Class<?>[] interfaces = proxy.getClass().getInterfaces();
for (Class<?> anInterface : interfaces) {
UseDataSource annotation = anInterface.getAnnotation(UseDataSource.class);
if (annotation != null) {
return annotation.value();
}
}
}
return null;
if (interfaces[0] != RowMapper.class) {
TableInfo tableInfo = TableInfoFactory.ofMapperClass(interfaces[0]);
if (tableInfo != null) {
String dataSourceKey = tableInfo.getDataSource();
if (StringUtil.isNotBlank(dataSourceKey)) {
return dataSourceKey;
}
}
}
return NULL_KEY;
});
return NULL_KEY.equals(result) ? null : result;
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2009-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.core.mybatis.binding;
import com.mybatisflex.core.mybatis.FlexConfiguration;
import org.apache.ibatis.session.SqlSession;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Lasse Voss
* @author Michael Yang
*/
public class FlexMapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MybatisMapperProxy.MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public FlexMapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MybatisMapperProxy.MapperMethodInvoker> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(FlexMapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession, FlexConfiguration configuration) {
final FlexMapperProxy<T> mapperProxy = new FlexMapperProxy<>(sqlSession, mapperInterface, methodCache, configuration);
return newInstance(mapperProxy);
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright 2009-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.core.mybatis.binding;
import com.mybatisflex.core.mybatis.FlexConfiguration;
import org.apache.ibatis.binding.BindingException;
import org.apache.ibatis.binding.MapperRegistry;
import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
import org.apache.ibatis.io.ResolverUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class FlexMapperRegistry extends MapperRegistry {
private final FlexConfiguration config;
private final Map<Class<?>, FlexMapperProxyFactory<?>> knownMappers = new ConcurrentHashMap<>();
public FlexMapperRegistry(FlexConfiguration config) {
super(config);
this.config = config;
}
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final FlexMapperProxyFactory<T> mapperProxyFactory = (FlexMapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession, config);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new FlexMapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
/**
* Gets the mappers.
*
* @return the mappers
* @since 3.2.2
*/
public Collection<Class<?>> getMappers() {
return Collections.unmodifiableCollection(knownMappers.keySet());
}
/**
* Adds the mappers.
*
* @param packageName the package name
* @param superType the super type
* @since 3.2.2
*/
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
/**
* Adds the mappers.
*
* @param packageName the package name
* @since 3.2.2
*/
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright 2009-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.core.mybatis.binding;
import com.mybatisflex.core.util.MapUtil;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;
import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
/**
* @author Clinton Begin
* @author Eduardo Macarron
* @author Michael Yang
* <p>
* 参考 MapperProxy<T> 并开放 MapperMethodInvoker方便子类代理
*/
public class MybatisMapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private static final Method privateLookupInMethod;
protected final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
public MybatisMapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
static {
Method privateLookupIn;
try {
privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
} catch (NoSuchMethodException e) {
privateLookupIn = null;
}
privateLookupInMethod = privateLookupIn;
Constructor<Lookup> lookup = null;
if (privateLookupInMethod == null) {
// JDK 1.8
try {
lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
lookup.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
e);
} catch (Exception e) {
lookup = null;
}
}
lookupConstructor = lookup;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
protected MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
if (!m.isDefault()) {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
}
return new DefaultMethodInvoker(getMethodHandleJava9(method));
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
private MethodHandle getMethodHandleJava9(Method method)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
declaringClass);
}
private MethodHandle getMethodHandleJava8(Method method)
throws IllegalAccessException, InstantiationException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
}
public interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
private static class DefaultMethodInvoker implements MapperMethodInvoker {
private final MethodHandle methodHandle;
public DefaultMethodInvoker(MethodHandle methodHandle) {
this.methodHandle = methodHandle;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return methodHandle.bindTo(proxy).invokeWithArguments(args);
}
}
}