Mybatis学习

为什么要使用MyBatis

没有MyBatis的时候连接数据库

JDBC连接

//注册JDBC驱动 
Class.forName("com.mysql.jdbc.Driver"); 
// 打开连接 
conn DriverManager.getConnection(DB_URL,USER,PASSWORD); 
//执行查询 
stmt conn.createStatement(); 
String sql = "select id,name from book";
ResultSet rs = stmt.executeQuery(sql);
while(rs.next()){
    ...
}

从代码上可以看出,JDBC连接将数据库操作功能会与业务功能耦合在一起,如果调整表结构、或者修改业务逻辑,工作量难以估计,且容易对正常功能产生影响。

同时对结果集的处理也必须根据字段一个一个匹配,增加很多重复的工作量。

Book book = new Book();
book.id = res.getInt("id");
book.name = rs.getString("name");

DbUtils

通过QueryRunner简单的封装将数据库的驱动、连接、执行与业务隔离,同时提供了ResultSetHandler进行结果集到POJO的转换。

缺点:DbUtils要求数据库字段跟对象的属性名称完全一致,否则转换失败。

Spring JDBC

Spring也对原生JDBC进行了封装,并提供了JdbcTemplate,来简化我们对数据库的操作。并提供了RowMapper接口将结果集转换成对象。

缺点:每个对象都需要定义一个Mapper并实现RowMapper接口,手动编写转换代码。

Hibernate

Hibernate通过xml方式配置了实体类和数据库的映射关系,提供session的增删查改方法。

//获取加载配置管理类
Configuration configuration = new Configuration();
//加载默认的hibernate.cfg.xml
configuration.configure();

//创建session工厂
SessionFactory factory = configuration.buildSessionFactory();
Session session = factory.openSession();
//开启事务
Transaction transaction = session.getTransaction();
transaction.begin();
doSomeThing();
transaction.commit();
session.close();

通过Hibernate我们操作数据库就跟操作对象一样,Hibernate帮我们自动生成SQL语句(屏蔽数据库的差异),自动映射实体类和数据表结构,代码更加简洁。

缺点:

  1. 调用get、save、update等方法时基于对象,操作所有字段,无法制定部分字段,不够灵活。
  2. 对于自动生成的SQL,想去优化非常困难。
  3. 不支持动态SQL,如:分表中表名变化等。

MyBatis

MyBatis跟Hibernate相比去掉了SQL的自动生成,同时通过xml文件将SQL语句与代码分离。

特性:

  1. 使用连接池对连接进行管理
  2. SQL和代码分离,集中管理
  3. 结果集映射
  4. 参数映射和动态SQL
  5. 重复SQL提取
  6. 缓存管理
  7. 插件机制
String resource = "mybatis-config.xml";
InputStream is = Resource.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = sqlSessionFactory.openSession();
BookMapper bookMapper = session.getMapper(BookMapper.class);
Book book = bookMapper.selectBookById(1);
session.close();

MyBatis

整体架构

接口层

  SqlSession是上层应用和MyBatis打交道的桥梁,定义了对数据库的操作方法。接口层在接收到调用请求时,会调用核心处理层的相应模块来完成具体的数据库操作。

核心处理层

  核心处理层主要负责:

  • 解析xml文件中的SQL语句,包括插入参数,动态生成SQL语句
  • 把接口出入的参数解析并映射成JDBC类型
  • 执行SQL语句
  • 处理结果集并映射成Java对象
  • 提供插件功能

基础支持层

  基础支持层主要是抽取通用的功能,用来支撑核心处理层的功能。

核心对象

  • Configuration
    包含MyBatis所有的配置信息,
  • SqlSessionFactoryBuilder
    用于构建SqlSessionFactory生命周期存在于方法。
  • SqlSessionFactory
    创建SqlSession用于应用程序访问数据库,生命周期为应用的生命周期,单例实现
  • SqlSession
    访问数据库的会话,线程不安全,不能在线程间共享。因此在操作完成后需要关闭,生命周期为请求或者操作中。
  • Executor
    执行器,是MyBatis调度的核心,负责SQL语句的生成和缓存的查询维护等工作。
  • StatementHandler
    封装具体的JDBC操作,设置参数,转换结果集等
  • ParameterHandle
    将用户的参数转换为JDBC Statement需要的参数
  • ResultSetHandler
    将JDBC返回的结果集对象转换为List类型的集合
  • MapperProxy
    代理Mapper接口
  • MappedStatement
    维护增删查改等SQL语句的封装

MyBatis缓存

  MyBatis提供了一级缓存和二级缓存,并且预留了集成第三方缓存的接口。MyBatis的缓存都在cache包里面,其中有一个Cache接口,同时提供了一个默认实现类PerpetualCache。此外通过装饰器,提供了很多额外的功能,如:回收策略、日志记录、定时刷新等。

缓存淘汰策略

  缓存淘汰基本分为三类:基本缓存、淘汰缓存、装饰器缓存。

  • PerpetualCache
    • 基本实现
  • LruCache
    • LRU策略,当缓存到达上限的时候,删除最少使用的缓存
  • FifoCache
    • FIFO策略,当缓存到达上限的时候,删除最先入队的缓存
  • SoftCache、WeakCache
    • 利用JVM的软引用、弱引用实现缓存,当JVM内存不足时会自动清理掉
  • LoggingCache
    • 带日志功能,可以用于缓存命中率计算
  • SynchronizedCache
    • 同步缓存,解决并发问题
  • BlockingCache
    • 阻塞缓存,
  • SerializedCache
  • ScheduledCache
  • TransactionalCache

一级缓存

  一级缓存也叫本地缓存,保存在SqlSession层面。一级缓存默认开启,且不需要任何配置。

  一级缓存在BaseExecutor的query(获取)和queryFromDatabase(保存)中,且在同一个会话的update调用clearLocalCache方法使缓存失效。

  一级缓存可以通过xml、注解的方式关闭。

<settings>
<setting name="cacheEnabled" value="false"/>
</settings>


@Options(flushCache = Options.FlushCachePolicy.TRUE)
@Select("select * from book where id = #{id,jdbcType=BIGINT}")
@ResultMap("resultMap")
Book find(Long id);

由于一级缓存是在会话中,在多会话或者分布式环境下,会存在脏数据的情况。为了解决这个问题,MyBatis提供了二级缓存。

二级缓存

  二级缓存的作用范围是namespace,可以被多个SqlSession共享。开启二级缓存MyBatis则使用CachingExecutorExecutor进行了装饰。CachingExecutor对于查询的请求会先判断是否有缓存结果如果有则直接返回,否则交给真正的查询器执行,如SimpleExecutor。开启方式:

  • 在mybatis-config.xml中配置cacheEnabled(默认是开启的,可以不配置)
<setting name="cacheEnabled" value="true"/>
  • 在Mapper.xml中配置cache标签
<cache type="org.apache.ibatis.cache.impl.PerpetualCache" size="1024" eviction="LRU" flushInterval="12000" readOnly="false"/>

  对于部分实时性不高的方法,不需要二级缓存可以在具体StatementId上显示关闭二级缓存useCache="false"默认是true。

二级缓存使用TCM来管理,因此在存在事务的情况下事务提交后才会生效。

二级缓存也存在脏读的情况,如表Book在两个namespace中存在,则一个namespace刷新,另一个则会出现脏读。

cache-ref

  MyBatis用cache-ref避免二级缓存脏读问题,cache-ref代表引用别的命名控件的Cache配置,即两个空间使用同一个Cache。

第三方缓存

  MyBatis可以集成第三方缓存方式。如redis、ehcahe等。

<cache type="org.mybatis.caches.redis.RedisCache" eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

配置文件

configuration

配置文件根目录,对应着MyBatis最重要的配置类Configuration。

properties

配置参数,如数据库连接信息,避免写死在xml中可以将参数单独放在properties文件中,通过properties标签引入${}.

settings

typeAliases

类型别名,通过typeAliases可以为我们自己的Bean创建别名。MyBatis里面有系统预先定义好的类型别名,在TypeAliasRegistry中。

typeHandlers

Java类型与数据库的JDBC类型不完全一致,我们可以通过MyBatis提供的typeHandlers进行转换。MyBatis内置了很多TypeHandler(type包下),已经在TypeHandlerRegistry中注册了。

如果我们需要自定义一套类型转换规则,或者在处理时做一些特殊动作,那么可以继承BaseTypeHander来编写自己的TypeHandler。

public class MyTypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
            throws SQLException {
        // 设置 String 类型的参数的时候调用,Java类型到JDBC类型
        ps.setString(i, parameter);
    }
    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // 根据列名获取 String 类型的参数的时候调用,JDBC类型到java类型
        return rs.getString(columnName);
    }
    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        // 根据下标获取 String 类型的参数的时候调用
        System.out.println("---------------getNullableResult2:"+columnIndex);
        return rs.getString(columnIndex);
    }
    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        System.out.println("---------------getNullableResult3:");
        return cs.getString(columnIndex);
    }
}

在config文件中注册:

<typeHandlers>
	<typeHandler handler="com.Test.MyTypeHandler"></typeHandler>
</typeHandlers>

在需要使用的字段上指定Handler:

<insert id="insertBook" parameterType="com.Test.domain.book">
	insert into book(name)values(#{name,jdbcType=VARCHAR,typeHandler=com.Test.MyTypeHandler})
</insert>

最后在resultMap上指定typeHandler:

<result column="name" property="name" jdbcType="VARCHAR" typeHandler="com.Test.MyTypeHandler"/>

objectFactory

MyBatis提供了一个工厂类接口(ObjectFactory)专门用来创建对象实例,用来处理数据库返回的结果集转换为实体类。

ObjectFactory有一个默认实现DefaultObjectFactory,通过调用instantiateClass(),利用反射实现。同样的我们可以自己定义一个类,实现ObjectFactory,来控制生成对象时的行为。

public class MyObjectFactory extends DefaultObjectFactory {
    @Override
    public Object create(Class type) {
        System.out.println("创建对象方法:" + type);
        if (type.equals(Book.class)) {
            Book book = (Book) super.create(type);
            blog.setName("myBook");
            return book;
        }
        Object result = super.create(type);
        return result;
    }
    public static void main(String[] args){
        MyObjectFactory myObjectFactory = new MyObjectFactory();
        Book book = myObjectFactory.create(Book.class);
    }
}

同时也可以在config文件里注册,这样在创建对象的时候会被自动调用:

<objectFactory type="com.Test.MyObjectFactory">
	<property name="name" value="myBook"/>
</objectFactory>

objectFactory.create()什么时候会调用?

创建对象和创建DefaultResultSetHandler的时候。

plugins

MyBatis预留了插件的接口让MyBatis更容易扩展,MyBatis提供了四个对象插件,Executor、ParameterHandler、ResultSetHandler、StatementHandler。

environment、environments

管理数据库环境,比如开发环境、测试环境、生产环境等。可以在不通给环境中使用不通的数据库地址或者类型。

<environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
</environments>

transactionManager

  • 配置JDBC则会使用Connection对象的commmit()、rollback()、close()管理事务。
  • 配置MANAGED则会交给容器管理,如Weblogic等。
  • 如果是Spring+MyBatis则无需配置,applicationContext.xml里的数据源配置会覆盖MyBatis的。

mappers

配置Mapper.xml的路径,让MyBatis启动的时候去扫描这些映射器,创建映射关系。

  • 使用相对于类路径的资源引用
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

  • 使用完全限定资源定位符(URL)
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

  • 使用映射器接口实现类的完全限定类名
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

  • 将包内的映射器接口实现全部注册为映射器
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>


MyBatis在哪个步骤拿到SQL语句?
SQL语句在MappedStatement中的SqlSource中,运行时通过getBoundSql()方法获取,MappedStatement由configuration的getMappedStatement()方法获取。

dataSource

settings

属性名

简介

有效值

默认值

cacheEnabled

所有映射器配置缓存的开关,全局缓存开关

true|false

true

lazyLoadingEnabled

控制是否延迟加载。特殊关系单独配置可以使用fetchType属性来覆盖

true|false

false

aggressiveLazyLoading

开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载

true|false

false

multipleResultSetsEnabled

是否允许单个语句返回多结果集(需要数据库驱动支持)。

true|false

true

useColumnLabel

使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。

true|false

true

useGeneratedKeys

允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。

true|false

False

autoMappingBehavior

指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。

NONE, PARTIAL, FULL

PARTIAL

autoMapping UnknownColumnBehavior

指定发现自动映射目标未知列(或未知属性类型)的行为。
- None:不做任何处理
- WARNING:输出警告
- FAILING:抛出异常

NONE, WARNING, FAILING

NONE

defaultExecutorType

配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。

SIMPLE REUSE BATCH

SIMPLE

defaultStatementTimeout

设置超时时间,它决定数据库驱动等待数据库响应的秒数。

任意正整数

未设置 (null)

defaultFetchSize

为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。

任意正整数

未设置 (null)

defaultResultSetType

指定语句默认的滚动策略。(新增于 3.5.2)

FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同于未设置)

未设置 (null)

safeRowBoundsEnabled

是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。

true | false

False

safeResultHandlerEnabled

是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。

true | false

True

mapUnderscoreToCamelCase

是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。

true | false

False

localCacheScope

MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。

SESSION | STATEMENT

SESSION

jdbcTypeForNull

当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。

JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。

OTHER

lazyLoadTriggerMethods

指定对象的哪些方法触发一次延迟加载。

用逗号分隔的方法列表。

equals,clone,hashCode,toString

defaultScriptingLanguage

指定动态 SQL 生成使用的默认脚本语言。

一个类型别名或全限定类名。

org.apache.ibatis.scripting.xmltags.XMLLanguageDriver

defaultEnumTypeHandler

指定 Enum 使用的默认 TypeHandler

。(新增于 3.4.5)

一个类型别名或全限定类名。

org.apache.ibatis.type.EnumTypeHandler

callSettersOnNulls

指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。

true | false

false

returnInstanceForEmptyRow

当返回行的所有列都是空时,MyBatis默认返回 null

。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2)

true | false

false

logPrefix

指定 MyBatis 增加到日志名称的前缀。

任何字符串

未设置

logImpl

指定 MyBatis 所用日志的具体实现,未指定时将自动查找。

SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

未设置

proxyFactory

指定 Mybatis 创建可延迟加载对象所用到的代理工具。

CGLIB | JAVASSIST

JAVASSIST (MyBatis 3.3 以上)

vfsImpl

指定 VFS 的实现

自定义 VFS 的实现的类全限定名,以逗号分隔。

未设置

useActualParamName

允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters

选项。(新增于 3.4.1)

true | false

true

configurationFactory

指定一个提供 Configuration

实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration()

的方法。(新增于 3.2.3)

一个类型别名或完全限定类名。

未设置

shrinkWhitespacesInSql

从SQL中删除多余的空格字符。请注意,这也会影响SQL中的文字字符串。 (新增于 3.5.5)

true | false

false

defaultSqlProviderType

Specifies an sql provider class that holds provider method (Since 3.5.6). This class apply to the type

(or value

) attribute on sql provider annotation(e.g. @SelectProvider

), when these attribute was omitted.

A type alias or fully qualified class name

Not set

Mapper.xml配置文件

cache

二级缓存配置

cache-ref

resultMap

数据集加载转换对象

sql

可重用语句块

<sql id="Base_Column_List">
	id,name
</sql>

操作标签

insert、update、delete、select、resultType、resultMap

关联查询通常配置resultMap或者修改dto,增加字段,或者引用关联对象(association等)

其他标签

if、choose(when、otherwise)、trim(where、set)、foreach

嵌套查询

  • 嵌套结果

<resultMap id="BookResultMap" type="com.Test.domain.book">
        <id column="id" property="id" jdbcType="INTEGER"/>
        <result column="name" property="name" jdbcType="VARCHAR"/>
        <!-- 联合查询,将author的属性映射到ResultMap -->
        <association property="author" javaType="com.Test.domain.Author">
            <id column="author_id" property="authorId"/>
            <result column="author_name" property="authorName"/>
        </association>
    </resultMap>

  • 嵌套查询

<resultMap id="BlogWithAuthorQueryMap" type="com.Test.domain.book">
        <id column="id" property="id" jdbcType="INTEGER"/>
        <result column="name" property="name" jdbcType="VARCHAR"/>
        <association property="author" javaType="com.Test.domain.Author" column="author_id" select="selectAuthor"/>
    </resultMap>
<!-- 嵌套查询 -->
    <select id="selectAuthor" parameterType="int" resultType="com.Test.domain.Author">
        select author_id authorId, author_name authorName
        from author where author_id = #{authorId}
    </select>

该方式存在N+1问题,即查询了一次员工,返回了多个员工信息时会再次查询N次部门信息。在MyBatis中可以通过延迟加载的方式解决。

<setting name="lazyLoadingEnabled" value="true"/><!--启用延迟加载-->
<setting name="aggressiveLazyLoading" value="false"/><!--是否对象的所有方法都会触发-->

翻页

逻辑分页

  MyBatis提供了RowBounds逻辑分页对象,里面有offset和limit两个属性。只需要在Mapper接口的方法上加上这个参数,不需要修改xml里面的SQL语句。通过skipRows(resultSet,rowBounds)会舍弃掉offset条数据,然后对剩下的数据取limit条。

如果数据量大,则存在效率问题(改方式等同于内存分页)。

物理分页

方法一、将limit和pageSize作为参数传给sql语句

<select id="selectBookPage" parameterType="map" resultMap="BaseResultMap">
        select * from book limit #{limit} , #{pageSize}
    </select>

该方法需要在代码里计算limit数据,需要在每个Statement里编写代码造成Mapper映射器代码冗余。

方法二、利用MyBatis拦截器实现,如PageHeler

PageHelper.startPage(0,10);
List<Book> books = bookService.list();
PageInfo page = new Page(books);

通用Mapper

通过编写泛型通用接口,如BaseMapper,把实体类作为参数传入。接口里定义大量的增删查改接口,方法支持泛型。自定义的Mapper接口继承通用接口,则可以避免大量类似的代码。改方式只适用于单表。

插件

  MyBatis使用代理模式和责任链模式来实现插件机制。允许Executor、StatementHandler、ParameterHandler、ResultSetHandler。

实现步骤:

  1. 实现Interceptor接口
  2. 添加@Interceptors({@Signature()}),指定拦截的方法和对象
  3. 实现接口方法interceptpluginsetProperties
  4. 在config文件中注册
<plugins>
	<plugin intercept="com.github.pagehelper.PageInterceptor">
        <property name="offsetAsPageNum" value="true"/>
    </plugin>
</plugins>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苜蓿花乐园

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值