一、SqlSessionFactory

用于在连接或数据源中创建SqlSession,是一个单纯的创建SqlSession的工厂接口。

  1. public interface SqlSessionFactory {
  2. SqlSession openSession();
  3. SqlSession openSession(boolean autoCommit);
  4. SqlSession openSession(Connection connection);
  5. SqlSession openSession(TransactionIsolationLevel level);
  6. SqlSession openSession(ExecutorType execType);
  7. SqlSession openSession(ExecutorType execType, boolean autoCommit);
  8. SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  9. SqlSession openSession(ExecutorType execType, Connection connection);
  10. Configuration getConfiguration();
  11. }

二、SqlSession

The primary Java interface for working with MyBatis. Through this interface you can execute commands, get mappers and manage transactions.

如下图可见,SqlSession封装了我们所有的增删改查操作,是一个很重要的接口
image.png

三、mapper的生成

1、猜想的生成过程

自旋可以回忆:https://www.yuque.com/wangchao-volk4/fdw9ek/xmkynl

  1. public class SupkingxLock {
  2. private static final Unsafe unsafe;
  3. private static final long valueOffset;
  4. private volatile int value = 0;
  5. static {
  6. try {
  7. Class<Unsafe> unsafeClass = Unsafe.class;
  8. Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
  9. theUnsafe.setAccessible(true);
  10. unsafe = (Unsafe) theUnsafe.get(null);
  11. valueOffset = unsafe.objectFieldOffset
  12. (SupkingxLock.class.getDeclaredField("value"));
  13. } catch (Exception ex) {
  14. throw new Error(ex);
  15. }
  16. }
  17. public void lock() {
  18. // 自旋
  19. for (; ; ) {
  20. // this 当前对象本身,valueOffset 位移偏移量,0:内存中的真值,1:将要变成的值
  21. if (unsafe.compareAndSwapInt(this, valueOffset, 0, 1)) {
  22. return;
  23. }
  24. // 如果不是自己想要的值,则将当前线程让出去
  25. Thread.yield();
  26. }
  27. }
  28. public void unlock() {
  29. value = 0;
  30. }
  31. public static void main(String[] args) {
  32. System.out.println(unsafe);
  33. }
  34. }
  1. public class MyBatisEnhance {
  2. public static final Map<Class<?>, Object> objectMap = new HashMap<>();
  3. private static SupkingxLock supkingxLock = new SupkingxLock();
  4. static class MapperScanner {
  5. private String mapperLocation;
  6. public List<Class<?>> scanMapper() {
  7. List<Class<?>> classList = new ArrayList<>();
  8. for (int i = 0; i < 500000000; i++) {
  9. classList.add(MyBatisEnhance.class);
  10. }
  11. return new ArrayList<>();
  12. }
  13. public String getMapperLocation() {
  14. return mapperLocation;
  15. }
  16. public void setMapperLocation(String mapperLocation) {
  17. this.mapperLocation = mapperLocation;
  18. }
  19. }
  20. /**
  21. * 可以优化来加个锁,实际Mybatis中并没有加锁
  22. * @param object
  23. */
  24. public static void put(Object object) {
  25. supkingxLock.lock();
  26. objectMap.put(object.getClass(), object);
  27. supkingxLock.unlock();
  28. }
  29. public static void main(String[] args) {
  30. String mapperLocation = "classpath:mapper/*.xml";
  31. MapperScanner mapperScanner = new MapperScanner();
  32. mapperScanner.setMapperLocation(mapperLocation);
  33. /**
  34. * 一千个??
  35. */
  36. List<Class<?>> classes = mapperScanner.scanMapper();
  37. // 解析xml
  38. // 。。。。。。。。
  39. long start = System.currentTimeMillis();
  40. // 将这1000个mapper添加到map中
  41. classes.forEach(aClass -> objectMap.put(aClass, new Object()));
  42. // classes.parallelStream().forEach(aClass -> put(new Object()));
  43. long end = System.currentTimeMillis();
  44. System.out.println(end - start);
  45. }
  46. }

2、MyBatis中mapper产生的实现方式

2.1 代码入口

  1. public static void main(String[] args) throws IOException {
  2. InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
  3. SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  4. SqlSessionFactory factory = builder.build(in);
  5. SqlSession session = factory.openSession();
  6. //获得dao 的代理对象
  7. UserMapper userMapper = session.getMapper(UserMapper.class);
  8. User user = userMapper.queryById(21);
  9. System.out.println(user.toString());
  10. }

通过上述代码,很明显可以只知道,第一步载入 xml 文件,是从 SqlSessionFactoryBuilder 开始的

2.2 SqlSessioinFactoryBuilder

这个类负责解析 xml 并 build出 SqlSessionFactory

2.2.1 解析xml的入口

org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)
image.png

2.2.2 解析 xml 并将 mapper(interface)放入到configuration中

image.png
parse—>parseConfiguration—>mapperElement—>configuration.addMappers(mapperPackage);
根据配置文件中 标签的配置方式,如果指定了 package,则直接configuration.addMappers;
如果是指定了标签mapper,则进入下图红框标出的位置,mapperParser.parse->bindMapperForNamespace
image.png
这个parse是用来解析写了sql的mapper.xml文件的
image.png
在 bindMapperForNamespace 方法中,通过书写了 sql 的 mapper.xml 文件中的 nameSpace 标签获取 mapper接口的全类名,然后通过反射获取到这个接口,最后将其 add 到 configuration 中
image.png
image.png

2.2.3 addMapper

addMapper方法就是将 接口mapper的位置作为key,value是一个封装了接口mapper的 MapperProxyFactory 对象。

  1. private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
  2. public void addMappers(String packageName, Class<?> superType) {
  3. ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
  4. resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
  5. Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
  6. for (Class<?> mapperClass : mapperSet) {
  7. addMapper(mapperClass);
  8. }
  9. }
  10. public <T> void addMapper(Class<T> type) {
  11. if (type.isInterface()) {
  12. if (hasMapper(type)) {
  13. throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
  14. }
  15. boolean loadCompleted = false;
  16. try {
  17. // 接口mapper的位置作为key,value是一个封装了接口mapper的 MapperProxyFactory 对象。
  18. knownMappers.put(type, new MapperProxyFactory<>(type));
  19. // It's important that the type is added before the parser is run
  20. // otherwise the binding may automatically be attempted by the
  21. // mapper parser. If the type is already known, it won't try.
  22. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
  23. parser.parse();
  24. loadCompleted = true;
  25. } finally {
  26. if (!loadCompleted) {
  27. knownMappers.remove(type);
  28. }
  29. }
  30. }
  31. }

2.2.4 MapperProxyFactory

  1. public class MapperProxyFactory<T> {
  2. private final Class<T> mapperInterface;
  3. private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
  4. public MapperProxyFactory(Class<T> mapperInterface) {
  5. this.mapperInterface = mapperInterface;
  6. }
  7. public Class<T> getMapperInterface() {
  8. return mapperInterface;
  9. }
  10. public Map<Method, MapperMethodInvoker> getMethodCache() {
  11. return methodCache;
  12. }
  13. @SuppressWarnings("unchecked")
  14. protected T newInstance(MapperProxy<T> mapperProxy) {
  15. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  16. }
  17. public T newInstance(SqlSession sqlSession) {
  18. final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  19. return newInstance(mapperProxy);
  20. }
  21. }

上述代码中的 MapperProxyFactory 中只是将 mapper 直接放到了 mapperInterface中,并没有区分这个mapper有注解的(@Select),还是用xml的。

这里接着 addMapper说
接着parser.parse()—>loadXmlResource()载入带sql的xml文件—->parseStatement(method),解析mapper接口上的注解方法,失败之后catch放入到configuration.addIncompleteMethod,然后再次parsePendingMethods执行失败的东西。
parse结束后,回到最上面 https://www.yuque.com/wangchao-volk4/fdw9ek/xpdqrv#lp5zo build返回 DefaultSqlSessionFactory 默认的。

3、是否可以优化 addMapper 方法

�这段代码并没用锁,也没有用ConcurrentHashMap。也没有使用 并行流(parallelStream().forEach(aClass -> put(new Object()) 的 这种添加方式。是不是可以优化一下???
什么时候应该用 parallelStream?可以参考现成的案例 java.util.Arrays#parallelSort(byte[])。
当数量小于 2的13次方或者并行数量是1的时候,用串行流,否则就要用并行流。

  1. /**
  2. * The minimum array length below which a parallel sorting
  3. * algorithm will not further partition the sorting task. Using
  4. * smaller sizes typically results in memory contention across
  5. * tasks that makes parallel speedups unlikely.
  6. */
  7. private static final int MIN_ARRAY_SORT_GRAN = 1 << 13;
  8. public static void parallelSort(byte[] a) {
  9. int n = a.length, p, g;
  10. if (n <= MIN_ARRAY_SORT_GRAN ||
  11. (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
  12. DualPivotQuicksort.sort(a, 0, n - 1);
  13. else
  14. new ArraysParallelSortHelpers.FJByte.Sorter
  15. (null, a, new byte[n], 0, n, 0,
  16. ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
  17. MIN_ARRAY_SORT_GRAN : g).invoke();
  18. }

四、获取mapper

  1. public static void main(String[] args) throws IOException {
  2. InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
  3. SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  4. SqlSessionFactory factory = builder.build(in);
  5. SqlSession session = factory.openSession();
  6. //获得dao 的代理对象
  7. UserMapper userMapper = session.getMapper(UserMapper.class);
  8. User user = userMapper.queryById(21);
  9. System.out.println(user.toString());
  10. }

2.2.3 结束之后,我们获取到了一个 SqlSessionFactory,接口用这个工厂生产一个 SqlSession。

1、openSession

image.png

  1. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  2. Transaction tx = null;
  3. try {
  4. // 获得配置
  5. final Environment environment = configuration.getEnvironment();
  6. // 从配置中获得事务
  7. final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  8. // 创建事务
  9. tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  10. // 创建执行器
  11. final Executor executor = configuration.newExecutor(tx, execType);
  12. return new DefaultSqlSession(configuration, executor, autoCommit);
  13. } catch (Exception e) {
  14. closeTransaction(tx); // may have fetched a connection so lets call close()
  15. throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
  16. } finally {
  17. ErrorContext.instance().reset();
  18. }
  19. }

2、getMapper

通过 创建的 SqlSession 中的 getMapper方法来获取mapper。其实就是获取 knownMappers 中的值(MapperProxyFactory)类型,接着,利用 MapperProxyFactory 中的 newInstance 方法创建一个代理对象(用的是jdk的proxy代理)。

knownMappers 中的值 是在 addMapper 时放入的

  1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  2. final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  3. if (mapperProxyFactory == null) {
  4. throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  5. }
  6. try {
  7. return mapperProxyFactory.newInstance(sqlSession);
  8. } catch (Exception e) {
  9. throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  10. }
  11. }

3、执行mapper

从MapperProxyFactory中获取到代理mapper后,就要开始执行。jdk的proxy的执行核心就是 InvocationHandler。请移步到下一节 3、MyBatis中关于mapper的执行过程