spring + mybatis实现数据库CRUD操作原理

事务

数据库事务

  • 事务:逻辑上的多条sql的分组。该分组内的事务要么全部成功(commit),要么全部失败(rollback)。
  • 数据库默认自动提交事务,即发一条sql就执行一条。
  • 单条sql语句不存在事务概念。
  • mysql事务语法:
start transaction
...
...
commit/rollback
- start transaction:开启事务
- rollback:回滚事务
- commit:提交事务

ACID

  • 原子性:事务内的一组sql,要么全部成功,要么全部失败
  • 一致性:事务执行之前和执行之后状态一致,e.g.拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
  • 隔离性:多个并发事务之间相互隔离,感知不到其他事务的存在
  • 持久性:事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作

隔离级别

没有隔离可能会出现的错误

  • 脏读:A事务读到B事务未提交的更改数据
  • 不可重复读:A事务读数据Data为状态S1, B事务将Data状态更新为S2并提交。A事务再次读Data,Data的状态
  • 幻读

JDBC事务

  • jdbc从数据库获取connection,默认情况下connection会自动向数据库提交它发送的sql。
  • jdbc事务流程:
// 关闭自动提交,相当于mysql的start transaction
connection.setAutoCommit(false); 
...
...
connection.commit()/rollback();

Spring事务

spring结合mybatis配置

  • spring数据库配置
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"
      p:basePackage="com.cmbchina.ccd.pluto.babylon.*.dao"/>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
      p:dataSource-ref="dataSource"
      p:configLocation="classpath:mybatis-config.xml"/>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
      p:dataSource-ref="dataSource"/>

<tx:annotation-driven transaction-manager="transactionManager"/>
  • 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>
    <!--<settings>-->
    <!--<setting name="logImpl" value="STDOUT_LOGGING"/>-->
    <!--</settings>-->

    <!-- 注入别名,便于mapper书写 -->
    <typeAliases>
        <package name="com.cmbchina.ccd.pluto.babylon.deploy.model"/>
        <package name="com.cmbchina.ccd.pluto.babylon.deploy.vo"/>
        <package name="com.cmbchina.ccd.pluto.babylon.resource.model"/>
        <package name="com.cmbchina.ccd.pluto.babylon.resource.vo"/>
    </typeAliases>

    <!--
        plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
        properties?, settings?,
        typeAliases?, typeHandlers?,
        objectFactory?,objectWrapperFactory?,
        plugins?,
        environments?, databaseIdProvider?, mappers?
    -->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <!-- 4.0.0以后版本可以不设置该参数 -->
            <property name="dialect" value="sqlserver"/>
            <!-- 该参数默认为false -->
            <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
            <!-- 和startPage中的pageNum效果一样-->
            <property name="offsetAsPageNum" value="true"/>
            <!-- 该参数默认为false -->
            <!-- 设置为true时,使用RowBounds分页会进行count查询 -->
            <property name="rowBoundsWithCount" value="true"/>
            <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
            <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
            <property name="pageSizeZero" value="true"/>
            <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
            <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
            <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
            <property name="reasonable" value="false"/>
            <!-- 支持通过Mapper接口参数来传递分页参数 -->
            <property name="supportMethodsArguments" value="false"/>
            <!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
            <property name="returnPageInfo" value="none"/>
        </plugin>
    </plugins>
</configuration>

mybatis技术内幕

SqlSession与SqlSessionFactory(https://my.oschina.net/zudajun/blog/665956)

  • SqlSessionFactory: SqlSession工厂bean,singleton,在spring启动时载入mybatis全局配置文件,创建SqlSessionFactory。
  • SqlSession: 程序与数据库交互的一次会话,SqlSession封装数据库增删改查及事务方法。
  • SqlSession生命周期:
    • 开启spring事务:一个事务共用一个SqlSession
    • 未开启spring事务:调用一次mybatis方法一个SqlSession

动态代理,自动映射Mapper的底层实现原理(https://my.oschina.net/zudajun/blog/666223)

  • Mybatis中声明一个interface接口,没有编写任何实现类,Mybatis就能返回接口实例,并调用接口方法返回数据库数据

实现原理:动态代理

  • 动态代理实现sql映射例子
/**
 * <i>invocation handler</i> of a proxy instance
 * Created by z673414 on 2018/6/29.
 *
 * @author z673414
 */
public class MapperProxy implements InvocationHandler {
    public static void main(String[] args) {
        MapperProxy mapperProxy = new MapperProxy();
        UserMapper userMapper = mapperProxy.newInstance(UserMapper.class);
        User user = userMapper.getUserById(1);

        System.out.println(user);
    }

    /**
     * Processes a method invocation on a proxy instance and returns
     * the result.  This method will be invoked on an invocation handler
     * when a method is invoked on a proxy instance that it is
     * associated with.
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                // 诸如hashCode()、toString()、equals()等方法,将target指向当前对象this
                return method.invoke(this, args);
            } catch (Exception t) {
                t.printStackTrace();
            }
        }

        return new User(1, "xiaoming");
    }

    /**
     * 创建proxy对象
     *
     * @param clz
     * @param <T>
     * @return
     */
    public <T> T newInstance(Class<T> clz) {
        return (T) Proxy.newProxyInstance(clz.getClassLoader(), new Class[]{clz}, this);
    }
}
  • mybatis动态代理的实现
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    ......
  }

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 调用sqlSession执行CRUD操作
    return mapperMethod.execute(sqlSession, args); 
  }

  private MapperMethod cachedMapperMethod(Method method) {
    ......
  }

}
- spring事务执行:


- Spring创建MapperScannerConfigurer bean,searches recursively starting from a base package for interfaces and registers them as {@code MapperFactoryBean},将sqlSessionFactory注入每个mapperFactoryBean
- MapperFactoryBean: BeanFactory that enables injection of MyBatis mapper interfaces
    - getSqlSession().getConfiguration().addMapper(this.mapperInterface)
- Spring创建SqlSessionFactory bean,解析配置文件生成Configuration对象
    - Configuration生成MapperRegistry对象
        - MapperRegistry getMapper()生成mapperProxyFactory
            - MapperProxyFactory newInstance()生成mapperInterface的代理对象,用于调用mybatis方法,执行数据库CRUD操作

猜你喜欢

转载自blog.csdn.net/weixin_41810396/article/details/80885727