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