4.Mybatis

作者: Young_5942 | 来源:发表于2020-09-05 17:17 被阅读0次

    简介

    Mybatis是一个ORM框架,ORM(Object relational Mapping)是对象关系映射模型,就是数据库的表和简单的java对象之间的映射。主要解决数据库数据和POJO对象的相互映射。通过这层映射关系可以简单迅速的把数据库表的数据转换为POJO。
    相对于其他的ORM,如Hibernate,Mybtis是一个半自动映射的框架,因为需要手工的匹配POJO,SQL和映射关系。而全自动的Hibernate只需要提供POJO和映射关系。

    ORM映射模型:
    java应用程序 ==> 映射配置 ===》数据库

    Mybatis的映射文件由以下三个部分组成:
    SQL、映射规则、POJO

    Mybatis的映射模型:
    Mybatis应用程序 ==> POJO ==> 接口、注解、sqlMapper、映射文件 ==> 数据库

    Mybatis的运行分为两大部分:一块是读取配置文件缓存到Configuration类中,用来创建SqlSessionFactory,第二块是SqlSession的执行过程。

    传统的JDBC编程

    1.使用JDBC编程需要连接数据库,注册驱动和数据库信息
    2.操作Connection,打开Statement对象
    3.通过Statement对象执行sql,返回结果到Resultset对象
    4.使用ResultSet读取数据,通过代码转换为POJO对象
    5.关闭数据库相关资源

    可以看出以下一个简单的查询就需要这么多的代码,如果更为复杂的应用、事务处理等等,ORM也正是基于JDBC的封装,简化我们编程。

    public class JDBCDemo {
    
        public static void main(String[] args) {
            JDBCDemo jdbcDemo = new JDBCDemo();
            Role role = jdbcDemo.getRole(1);
            System.out.println(role.id + " " + role.roleName + " " + role.note);
        }
    
        private Connection getConnection() {
            Connection connection = null;
            try {
                Class.forName("com.mysql.jdbc.Driver");
                String url = "jdbc:mysql://localhost:3306/mybatis";
                String user = "root";
                String pwd = "123456";
                connection = DriverManager.getConnection(url, user, pwd);
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            }
            return connection;
        }
    
        public Role getRole(int id) {
            Connection connection = getConnection();
            ResultSet rs = null;
            PreparedStatement ps = null;
            try {
                ps = connection.prepareStatement("select id,role_name,note from t_role where id = ?");
                ps.setInt(1, id);
                rs = ps.executeQuery();
                while (rs.next()) {
                    int id1 = rs.getInt("id");
                    String roleName = rs.getString("role_name");
                    String note = rs.getString("note");
                    Role role = new Role();
                    role.id = id1;
                    role.roleName = roleName;
                    role.note = note;
                    return role;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                this.close(rs, ps, connection);
            }
            return null;
        }
    
        private void close(ResultSet rs, PreparedStatement ps, Connection connection) {
            try {
                if (rs != null && !rs.isClosed()) {
                    rs.close();
                }
                if (ps != null && !ps.isClosed()) {
                    ps.close();
                }
                if (connection != null && !connection.isClosed()) {
                    connection.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        class Role {
            int id;
            String roleName;
            String note;
        }
    }
    

    使用Mybatis实现以上功能

    配置文件 config.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
      <!--定义数据库信息-->
        <environments default="development">
            <environment id="development">
                <!--定义事务管理器-->
                <transactionManager type="JDBC"></transactionManager>
                <!--定义数据库连接-->
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
    <!--定义映射器-->
        <mappers>
            <mapper resource="mapper/TRoleMapper.xml"></mapper>
        </mappers>
    </configuration>
    
    映射规则文件 TRoleMapper.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.example.demo.mapper.TRoleMapper">
        <select id="getRoleById" parameterType="int" resultType="com.example.demo.base.Role">
            select id,role_name as roleName,note from t_role where id = #{id};
        </select>
    </mapper>
    
    Mapper TRoleMapper.java
    public interface TRoleMapper {
        Role getRoleById(int id);
    }
    
    Mybatis 应用程序、测试类
    public class MybatisDemo {
    
        static SqlSessionFactory sqlSessionFactory = null;
    
        public static void main(String[] args) {
            SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
            SqlSession sqlSession = sqlSessionFactory.openSession();
            TRoleMapper mapper = sqlSession.getMapper(TRoleMapper.class);
            Role roleById = mapper.getRoleById(1);
            System.out.println("roleName : " + roleById.getRoleName());
        }
    
        static SqlSessionFactory getSqlSessionFactory() {
            if (null == sqlSessionFactory) {
                try {
                    String resource = "config.xml";
                    sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(resource));
                    return sqlSessionFactory;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return sqlSessionFactory;
        }
    }
    

    Mybatis 基本构成

    核心组件有:
    SqlSessionFactoryBuilder(构造器):根据配置信息或者代码来生成SqlSessionFactory(工厂接口)。
    SqlSessionFactory:依靠工厂来生成SqlSession(会话)。
    SqlSession:是一个即可以发送SQL去执行并返回结果,也可以获取mapper的接口。类似以JDBC的Connection对象。
    Sql Mapper:由一个java接口和xml文件(或者注解)构成,需要给出对应的sql和映射规则,负责发送sql去执行,并返回结果。
    执行过程:

    SqlSessionFactoryBuilder =》SqlSessionFactory=>SqlSession=>数据库
    SqlSessionFactoryBuilder =》SqlSessionFactory=>SqlSession=>SQL Mapper=>数据库
    

    SqlSessionFactory类

    Mybatis应用都是以SqlSessionFactory的实例为中心,通过SqlSessionFactoryBuilder获得,可以通过xml配置方式获取,也可以通过硬编码方式获取SqlSessionFactory,主要方法有获取SqlSession、获取Configuration类。
    默认的实现类是DefaultSqlSessionFactory

    public interface SqlSessionFactory {
      SqlSession openSession();
      SqlSession openSession(boolean autoCommit);
      SqlSession openSession(Connection connection);
      SqlSession openSession(TransactionIsolationLevel level);
      SqlSession openSession(ExecutorType execType);
      SqlSession openSession(ExecutorType execType, boolean autoCommit);
      SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
      SqlSession openSession(ExecutorType execType, Connection connection);
      Configuration getConfiguration();
    }
    

    Configuration 类

    Configuration存在Mybatis应用的整个生命周期中,以便于重复的读取和使用。在应用启动时候解析XML配置文件,保存到Configuration类中。
    如: protected final Map<String, MappedStatement> mappedStatements
    key为mapper的名称+方法名称,为后续的动态代理执行过程中查找相应的MappedStatement

    SqlSession类

    SqlSession也是一个接口类,真正的执行是Executor接口。

    public interface SqlSession extends Closeable {
    
      <T> T selectOne(String statement);
    
      <T> T selectOne(String statement, Object parameter);
    
      <E> List<E> selectList(String statement);
    
      <E> List<E> selectList(String statement, Object parameter);
    
      <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
    
      <K, V> Map<K, V> selectMap(String statement, String mapKey);
    
      <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
    
      <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
    
      <T> Cursor<T> selectCursor(String statement);
    
      <T> Cursor<T> selectCursor(String statement, Object parameter);
    
      <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
    
      void select(String statement, Object parameter, ResultHandler handler);
    
      void select(String statement, ResultHandler handler);
    
      void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
    
      int insert(String statement);
    
      int insert(String statement, Object parameter);
    
      int update(String statement);
    
      int update(String statement, Object parameter);
    
      int delete(String statement);
    
      int delete(String statement, Object parameter);
    
      void commit();
    
      void commit(boolean force);
    
      void rollback();
    
      void rollback(boolean force);
    
      List<BatchResult> flushStatements();
    
      @Override
      void close();
    
      void clearCache();
    
      Configuration getConfiguration();
    
      <T> T getMapper(Class<T> type);
    
      Connection getConnection();
    }
    

    Mybatis底层使用到了反射、动态代理等技术。

    JDK动态代理:
    一个Demo

    public interface HelloService {
        public void sayHello(String name);
    }
    
    public class HelloServiceImpl implements HelloService {
    
        @Override
        public void sayHello(String name) {
            System.out.println("hello " + name);
        }
    }
    
    
    public class HelloServiceProxy implements InvocationHandler {
    
        private Object target;
    
    
        public Object bind(Object target) {
            this.target = target;
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("代理类方法开始执行》》》》》》》》》");
            System.out.println("执行前》》》》》》》");
            Object invoke = method.invoke(target, args);
            System.out.println("执行后》》》》》》》");
            return invoke;
        }
    }
    
    public class JDKDynamicProxyDemo {
    
        public static void main(String[] args) {
            HelloServiceProxy helloServiceProxy = new HelloServiceProxy();
            HelloService proxy = (HelloService) helloServiceProxy.bind(new HelloServiceImpl());
            proxy.sayHello("xxxxxx");
        }
    }
    

    运行结果

    代理类方法开始执行》》》》》》》》》
    执行前》》》》》》》
    hello xxxxx
    执行后》》》》》》》
    

    Mapper的动态代理

    MapperProxyFactory

    public class MapperProxyFactory<T> {
    
      private final Class<T> mapperInterface;
      private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
    
      public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
      }
    
      public Class<T> getMapperInterface() {
        return mapperInterface;
      }
    
      public Map<Method, MapperMethodInvoker> getMethodCache() {
        return methodCache;
      }
    
      @SuppressWarnings("unchecked")
      protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    
      public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    }
    

    可以看出这里对接口的绑定。代理的方法在MapperProxy中

    MapperProxy#invoke()

    首先会判断是否是一个类,mapper是一个接口,判断为false生成MapperMethod对象,然后执行execute方法,把sqlsession和当前运行的参数传递进去。

    public class MapperProxy<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;
      private final SqlSession sqlSession;
      private final Class<T> mapperInterface;
      private final Map<Method, MapperMethodInvoker> methodCache;
    
      public MapperProxy(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);
          } else {
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      }
    
      private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
          // A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
          // It should be removed once the fix is backported to Java 8 or
          // MyBatis drops Java 8 support. See gh-1929
          MapperMethodInvoker invoker = methodCache.get(method);
          if (invoker != null) {
            return invoker;
          }
    
          return methodCache.computeIfAbsent(method, m -> {
            if (m.isDefault()) {
              try {
                if (privateLookupInMethod == null) {
                  return new DefaultMethodInvoker(getMethodHandleJava8(method));
                } else {
                  return new DefaultMethodInvoker(getMethodHandleJava9(method));
                }
              } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                  | NoSuchMethodException e) {
                throw new RuntimeException(e);
              }
            } else {
              return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
            }
          });
        } 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);
      }
    
      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) {
          super();
          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) {
          super();
          this.methodHandle = methodHandle;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
          return methodHandle.bindTo(proxy).invokeWithArguments(args);
        }
      }
    }
    
    MapperMethod#execute()

    MapperMethod采用命令模式,会跳转到不同的方法执行

    public class MapperMethod {
    
      private final SqlCommand command;
      private final MethodSignature method;
    
      public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new SqlCommand(config, mapperInterface, method);
        this.method = new MethodSignature(config, mapperInterface, method);
      }
    
      public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
          case INSERT: {
            Object param = method.convertArgsToSqlCommandParam(args);
            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:
            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());
        }
        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;
      }
    }
    

    Mybatis-Spring

    SpringAop
    以角色服务类(RoleServiceImpl)为例:
    Spring为RoleServiceImpl生成代理类,这样调用insertRole方法的时候进入一个invoke方法里面。spring会判断是否要拦截这个方法,这是一个切入点配置的问题,通过正则表达式配置,比如正则配置 insert*这样的通配,那么Spring就会拦截这个方法,否则不拦截,直接通过invoke方法反射调用这个方法,就结束了。

    其次是切面,当插入角色,包含了事务,这个事务就是整个方法的一个切面。可能这个方法比较复杂,包含了很多个操作,但是它们都受同一个事物管理,那么事务就是这方法的一个切面,这个时候Spring会根据我们的配置信息,知道这个方法需要事务,传播行为,回滚异常 等等。

    再次就是连接点,连接点根据在程序中根据不同的通知来实现的程序段。由于Spring采用了动态代理,使得可以在反射原始的方法之前做一些事情,于是有了前置通知(Before Advice)在反射之后做一些事情(After Adivce),反射原来方法可能正常返回,也可能抛出异常,就有了异常后通知(After throwing Adivce),也有可能用自定义方法 取代原有的方法,不采用原有的invoke方法,而使用自定义方法,所以还有环绕通知(Around Advice)。

    代理目标,就是哪个类对象被代理了,显然这里是RoleServiceImpl对象被代理了。

    Aop代理(Aop Proxy)就是指采用何种方式来代理,我们知道JDK的代理需要使用接口,而CGLIB不需要,Spring默认采用这样的规则,当Sping的服务包含接口描述时采用JDK动态代理,否则采用CGLIB代理。当然可以通过配置修改。

    事务的隔离级别:
    脏读 ==》 读写提交 ==》可重复读 ==》序列化

    Spring定义了7中传播行为:
    传播行为,指方法之间的调用问题。大部分情况下,我们认为事务应该都是一次性全部成功或者全部失败。但是有一些特例,不需要回滚全部,需要回滚部分的情况,就需要方法直接使用独立的事务控制,即这里设置不同的传播级别。

    Spring默认的传播级别:
    PROPAGETION_REQUIRED :如果存在一个事务,则支持当前事务,如果没有则开启
    PROPAGETION_SUPPORTS: 如果存在一个事务,则支持当前事务,如果没有
    PROPAGATION_MANDATORY:如果已经存在一个事务,则支持当前事务,如果没有一个活动事务,则抛出异常
    PROPAGATION_REQUERY_NEW:总是开启一个新事务,如果一个事务已经存在,则将这个存在的事务挂起
    PROPAGATION_NOT_SUPPORT:总是以非事务的方式执行,如果存在一个活动事务,则抛出异常
    PROPAGETION_NEVER:总是以非事务的方式执行 ,如果存在一个事务,则抛出异常
    PROPAGATION_NESTED : 如果一个活动的事务存在,则运行在一个嵌套事务中,如果没有活动的事务,则按照默认事务属性执行

    相关文章

      网友评论

          本文标题:4.Mybatis

          本文链接:https://www.haomeiwen.com/subject/zvqfektx.html