Mybatis-应用分析和最佳实践-核心配置

为什么要用Mybatis

  • 代码重复
  • 结果集处理太复杂
  • 连接管理

常见的工具

  • DbUtils
    • 数据源的支持
    • QueryRunner
      • 直接使用QueryRunner来查询
        • 提供了Resulthandler
        • 通过反射来做属性的映射
  • JDBCTemplate
    • 提供了数据源的支持
    • RowMapper来使用结果的处理
      • RowMapper提供泛型,避免每次创建对应的实现类
        • mapRow

上述没有解决的问题

  • 直接把SQL写在了代码里面
  • 条件只能按照顺序传入
  • 没有实现实体类到数据库记录的映射
  • 没有提供缓存等功能

ORM框架

  • O
    • 对象
  • M
    • 映射
  • R
    • 关系型数据库

  • Hibernate
    • 直接利用session#save

问题

  • 不能指定部分字段
  • 无法自定义SQL,优化困难
  • 不支持动态SQL

关于选型

  1. 业务简单的项目用Hibernate
  2. 需要灵活的SQL,使用Mybatis
  3. 对于性能要求高,使用JDBC
  4. Spring JDBC 可以和ORM框架混用

Mybatis

特性

  1. 连接池对连接进行管理
  2. SQL和代码分离,集中管理
  3. 参数映射和动态SQl
  4. 结果集映射
  5. 缓存管理
  6. 重复SQL的提取
  7. 插件机制

Mybatis编程式开发

  1. 导入依赖
  2. 指定配置
    1. mybatis-config.xml
    2. 读取配置
  3. 通过会话工厂构建器SqlsessionFactoryBuild创建SqlsessionFactory
  4. 创建会话session
  5. 通过session获取Mapper
    1. BlogMapper.class
    2. mappedstatement

核心对象作用域

作用域查看地址

SqlsessionFactoryBuilder

​ 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

​ 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlsessionFactory

​ SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。

​ 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。

​ 因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式

SqlSession

​ 每个线程都应该有它自己的 SqlSession 实例。

​ SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域

绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的应用逻辑代码
}

Mapper

​ 映射器是一些绑定映射语句的接口。

​ 映射器接口的实例是从 SqlSession 中获得的。虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。

​ 也就是说,映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。 映射器实例并不需要被显式地关闭。

​ 尽管在整个请求作用域保留映射器实例不会有什么问题,但是你很快会发现,在这个作用域上管理太多像 SqlSession 的资源会让你忙不过来。 因此,最好将映射器放在方法作用域内。就像下面的例子一样:

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 你的应用逻辑代码
}

核心配置

https://mybatis.org/mybatis-3/zh/configuration.html

Properties

​ 这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。具体细节参考文档

https://mybatis.org/mybatis-3/zh/configuration.html#settings

完整的settings

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

类型别名(typeAliases)

可以让我们在使用的时候简写

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

使用的时候直接简写就行了

也可以直接指定包名

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

内置类型别名

  • 基本数据类型(在前面+_)

    • _byte->byte
    • 。。。
  • 包装类(直接小写)

    • byte->Byte
  • 其他默认类型(全小写)

    date Date
    decimal BigDecimal
    bigdecimal BigDecimal
    object Object
    map Map
    hashmap HashMap
    list List
    arraylist ArrayList
    collection Collection
    iterator Iterator

类型处理器

Java数据类型->数据库类型

TypeHandlerRegistry

从代码可以看到注册了很多类型转化器

public TypeHandlerRegistry() {
  register(Boolean.class, new BooleanTypeHandler());
  register(boolean.class, new BooleanTypeHandler());
  register(JdbcType.BOOLEAN, new BooleanTypeHandler());
  register(JdbcType.BIT, new BooleanTypeHandler());

  register(Byte.class, new ByteTypeHandler());
  register(byte.class, new ByteTypeHandler());
  register(JdbcType.TINYINT, new ByteTypeHandler());

  register(Short.class, new ShortTypeHandler());
  register(short.class, new ShortTypeHandler());
  register(JdbcType.SMALLINT, new ShortTypeHandler());

  register(Integer.class, new IntegerTypeHandler());
  register(int.class, new IntegerTypeHandler());
  register(JdbcType.INTEGER, new IntegerTypeHandler());

  register(Long.class, new LongTypeHandler());
  register(long.class, new LongTypeHandler());

  register(Float.class, new FloatTypeHandler());
  register(float.class, new FloatTypeHandler());
  register(JdbcType.FLOAT, new FloatTypeHandler());

  register(Double.class, new DoubleTypeHandler());
  register(double.class, new DoubleTypeHandler());
  register(JdbcType.DOUBLE, new DoubleTypeHandler());

  register(Reader.class, new ClobReaderTypeHandler());
  register(String.class, new StringTypeHandler());
  register(String.class, JdbcType.CHAR, new StringTypeHandler());
  register(String.class, JdbcType.CLOB, new ClobTypeHandler());
  register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
  register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
  register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
  register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
  register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
  register(JdbcType.CHAR, new StringTypeHandler());
  register(JdbcType.VARCHAR, new StringTypeHandler());
  register(JdbcType.CLOB, new ClobTypeHandler());
  register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
  register(JdbcType.NVARCHAR, new NStringTypeHandler());
  register(JdbcType.NCHAR, new NStringTypeHandler());
  register(JdbcType.NCLOB, new NClobTypeHandler());

  register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
  register(JdbcType.ARRAY, new ArrayTypeHandler());

  register(BigInteger.class, new BigIntegerTypeHandler());
  register(JdbcType.BIGINT, new LongTypeHandler());

  register(BigDecimal.class, new BigDecimalTypeHandler());
  register(JdbcType.REAL, new BigDecimalTypeHandler());
  register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
  register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

  register(InputStream.class, new BlobInputStreamTypeHandler());
  register(Byte[].class, new ByteObjectArrayTypeHandler());
  register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
  register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
  register(byte[].class, new ByteArrayTypeHandler());
  register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
  register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
  register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
  register(JdbcType.BLOB, new BlobTypeHandler());

  register(Object.class, UNKNOWN_TYPE_HANDLER);
  register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
  register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);

  register(Date.class, new DateTypeHandler());
  register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
  register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
  register(JdbcType.TIMESTAMP, new DateTypeHandler());
  register(JdbcType.DATE, new DateOnlyTypeHandler());
  register(JdbcType.TIME, new TimeOnlyTypeHandler());

  register(java.sql.Date.class, new SqlDateTypeHandler());
  register(java.sql.Time.class, new SqlTimeTypeHandler());
  register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

  // mybatis-typehandlers-jsr310
  if (Jdk.dateAndTimeApiExists) {
    this.register(Instant.class, InstantTypeHandler.class);
    this.register(LocalDateTime.class, LocalDateTimeTypeHandler.class);
    this.register(LocalDate.class, LocalDateTypeHandler.class);
    this.register(LocalTime.class, LocalTimeTypeHandler.class);
    this.register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);
    this.register(OffsetTime.class, OffsetTimeTypeHandler.class);
    this.register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);
    this.register(Month.class, MonthTypeHandler.class);
    this.register(Year.class, YearTypeHandler.class);
    this.register(YearMonth.class, YearMonthTypeHandler.class);
    this.register(JapaneseDate.class, JapaneseDateTypeHandler.class);
  }

  // issue #273
  register(Character.class, new CharacterTypeHandler());
  register(char.class, new CharacterTypeHandler());
}

BooleanTypeHandler

public class BooleanTypeHandler extends BaseTypeHandler<Boolean> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType)
    throws SQLException {
    ps.setBoolean(i, parameter);
  }

  @Override
  public Boolean getNullableResult(ResultSet rs, String columnName)
    throws SQLException {
    return rs.getBoolean(columnName);
  }

  @Override
  public Boolean getNullableResult(ResultSet rs, int columnIndex)
    throws SQLException {
    return rs.getBoolean(columnIndex);
  }

  @Override
  public Boolean getNullableResult(CallableStatement cs, int columnIndex)
    throws SQLException {
    return cs.getBoolean(columnIndex);
  }
}
  • 我们可以创建自己的类型转化器
    • JDBC->JAVA
      • 列名来转化结果
      • 下标来转化结果
    • JAVA->JDBC类型
      • 参数设置

数据库提供了Json类型,我们需要做转化,就需要自己来写自己的JSON类型

插入的时候,我们可以在语句指定TypeHandler

#{name,jdbcType=VARCHAR,typeHandler=com.gupaoedu.type.MyTypeHandler}

在ResultMap指定TypeHandler

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

对象工厂(objectFactory)

每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。

默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。比如:

public class ExampleObjectFactory extends DefaultObjectFactory {
  public Object create(Class type) {
    return super.create(type);
  }
  public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
    return super.create(type, constructorArgTypes, constructorArgs);
  }
  public void setProperties(Properties properties) {
    super.setProperties(properties);
  }
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }
}
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

插件(plugins)

环境配置(environments)

  • 我们通过环境可以用来区分不同环境的配置
  • 不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

Mappers

  • 导入的方式

类路径的资源导入

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

完全限定资源定位符URL

<!-- 使用完全限定资源定位符(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>

我的笔记仓库地址gitee 快来给我点个Star吧

上一篇:mybatis模糊查询的sql语句的标准写法


下一篇:SQL模糊查询 同时匹配多个字段