MyBatis入门——了解配置

原文链接:https://www.dubby.cn/detail.html?id=9094

1、mybatis-config.xml

这个配置文件的结构如下:

  • properties
  • settings
  • typeAliases
  • typeHandlers
  • objectFactory
  • plugins
  • environments
    • environment
      • transactionManager
      • dataSource
  • databaseIdProvider
  • mappers

这里我简单说一说这些配置的意义,当然不可能每个都介绍的非常详细,我说说一些比较实用和比较常用的吧。

properties

这个其实是很多组件都会提供的一个配置功能,在这里可以可以定义一些属性,那么在这个文件的后面都可以使用${propertyName}来引用这个属性,而且这里还可以引用外部文件,比如数据库连接信息,一般属于高密配置,不能直接写在配置文件里,那么可以单独写个配置文件config.properties:

username=test
password=123456
url=jdbc:mysql://localhost:3306/mybatis_study?useUnicode=true&characterEncoding=UTF-8&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false

然后在mybatis-config.xml里配置:

<properties resource="config.properties">
    <!--默认不开启-->
    <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
    <!--默认就是:-->
    <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="#|#"/>
    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
</properties>

那么,在后续的配置中可以引用这些属性:

<dataSource type="cn.dubby.mybatis.configuration.datasource.HikariCPDataSourceFactory">
    <!--since MyBatis 3.4.2 如果driver没有找到,会使用冒号后的值作为默认值。此功能默认不开启,开启需指定org.apache.ibatis.parsing.PropertyParser.enable-default-value-->
    <property name="driverClassName" value="${driver#|#com.mysql.cj.jdbc.Driver}"/>
    <property name="jdbcUrl" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</dataSource>

注意看,org.apache.ibatis.parsing.PropertyParser.enable-default-valueorg.apache.ibatis.parsing.PropertyParser.default-value-separator这两个需要配合起来工作。作用是在使用${}引用属性时,如果没有找到这个属性,可以使用一个默认值来代替,而属性名和默认值中间的分隔符就是这里配置的#|#,默认是:,使用时是${driver#|#com.mysql.cj.jdbc.Driver},意思就是没有没有配置driver,那就使用com.mysql.cj.jdbc.Driver

如果即使用<property name="propertyName" value="propertyValue"/>,又在config.properties配置了propertyName,那么该以哪个为准呢?

config.properties里配置的为准!

settings

先给出一个setting的配置选项吧,我们来感受一下:

<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"/>
  <!--数据库里下划线自动匹配成Java中驼峰命名的属性-->
  <setting name="mapUnderscoreToCamelCase" value="true"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods"
    value="equals,clone,hashCode,toString"/>
</settings>

typeAliases

这是类型别名,因为Java中类的全限定名是包名+类名,太长啦,所以这里允许我们给他起个别名,这样在后面使用的时候,可以简短一点。

<typeAliases>
    <!--可以单独指定别名-->
    <typeAlias type="cn.dubby.mybatis.configuration.entity.Blog" alias="Blog"/>
    <!--也可以直接指定包下所有类的别名,此时还可以使用@Alias("blog")来修改他的别名-->
    <package name="cn.dubby.mybatis.configuration.entity"/>
</typeAliases>

可以一个类一个类的指定;也可以直接指定一个package,这样这个package下的所有的类都有别名了,此时你可以使用@Alias("blog")来修改他的别名,默认就是类名。

typeHandlers

在MyBatis中,数据库中列转化成Object中的变量,都是TypeHandler的转换成果。他是一个接口:

public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

MyBatis内置了很多TypeHandler,如果我们需要自定义呢?这里给个简单的例子,如果在数据库中手机号存储格式如下:

image

但是在Java中,mobile是由countryCode和mobile共同决定的:

public class MobilePhone {

    private static final String DEFAULT_COUNTRY_CODE = "86";

    private String countryCode;

    private String mobile;

    public MobilePhone(String mobile) {
        this(DEFAULT_COUNTRY_CODE, mobile);
    }

    public MobilePhone(String countryCode, String mobile) {
        this.countryCode = countryCode;
        this.mobile = mobile;
    }

    @Override
    public String toString() {
        return "'" + countryCode + "_" + mobile + "'";
    }
}

那么,我们可以自定义一个MobilePhoneHandler:

@MappedJdbcTypes(JdbcType.VARCHAR)
public class MobilePhoneHandler extends BaseTypeHandler<MobilePhone> {

    public void setNonNullParameter(PreparedStatement ps, int i, MobilePhone parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter.toString());
    }

    public MobilePhone getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return convert(rs.getString(columnName));
    }

    public MobilePhone getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return convert(rs.getString(columnIndex));
    }

    public MobilePhone getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return convert(cs.getString(columnIndex));
    }

    private MobilePhone convert(String mobileInDB) {
        if (mobileInDB == null || mobileInDB.trim().length() == 0)
            return null;

        String[] mobilePair = mobileInDB.split("_");
        if (mobilePair.length != 2) {
            throw new IllegalArgumentException("mobile phone must be consist of countryCode and mobileNo.");
        }
        return new MobilePhone(mobilePair[0], mobilePair[1]);
    }
}

然后别忘了需要配置这个TypeHandler

<typeHandlers>
    <!--可以单独指定别名-->
    <typeHandler handler="cn.dubby.mybatis.configuration.handler.MobilePhoneHandler"/>
    <!--也可以直接指定包下所有类的别名-->
    <package name="cn.dubby.mybatis.configuration.handler"/>
</typeHandlers>

那如果是枚举呢?

MyBatis给我们提供了两种枚举相关的TypeHandler:EnumTypeHandlerEnumOrdinalTypeHandler。一看就知道,一个是根据名字转换(String),一个是根据顺序转换(int)。

默认MyBatis是使用名字转化的,也就是如果你的枚举是这样的:

public enum CategoryEnum {
    A, B;
}

那么数据库中就存着A,B这样的字符串。但是很多DBA会建议我们使用Int来存储(性能,空间等各方面考虑),那么我们就需要使用EnumOrdinalTypeHandler了。

<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="cn.dubby.mybatis.configuration.enums.CategoryEnum"/>

可是,如果还想用String转换的方式呢,比如这种场景:

public class Category {
    private Integer id;
    private CategoryEnum category1;
    private CategoryEnum category2;
}

那可以在ResultMap在具体指定使用哪个来转换:

<resultMap id="CategoryResult" type="Category">
    <result column="id" property="id"/>
    <result column="category1" property="category1"/>
    <result column="category2" property="category2" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
</resultMap>

Enum顺序是从0开始的

objectFactory

MyBatis每次创建一个对象来承载查询结果时,都会使用ObjectFactory来创建。我们可以重写这个类,来实现我们的目的:

public class ExampleObjectFactory extends DefaultObjectFactory {

    private static final Logger logger = LoggerFactory.getLogger(ExampleObjectFactory.class);

    public <T> T create(Class<T> type) {
        logger.info("ExampleObjectFactory create a object, type is {}", type.getName());
        return super.create(type);
    }

    public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        logger.info("ExampleObjectFactory create a object, type is {}", type.getName());
        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);
    }

}

plugins

插件是MyBatis给我们开放的一个秘密通道,我们可以通过各个插件来拦截MyBatis生命周期里比较重要的一些方法调用:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

这里给个例子吧:

@Intercepts({@Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class ExamplePlugin implements Interceptor {

    private Logger logger = LoggerFactory.getLogger(ExamplePlugin.class);

    public Object intercept(Invocation invocation) throws Throwable {
        logger.info("====intercept start====");
        logger.info("参数:{}", invocation.getArgs());
        Object result = invocation.proceed();
        logger.info("结果:{}", result);
        logger.info("====intercept complete====");
        return result;
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
    }
}

配置插件:

<plugins>
    <plugin interceptor="cn.dubby.mybatis.configuration.plugin.ExamplePlugin">
        <property name="someProperty" value="100"/>
    </plugin>
</plugins>

environments

先预览一下:

<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>

首先我们看到一个environment,还有一个default="development"。这个作用是可以配置多个环境的数据源,每个envonment有个名字,还可以设置一个默认的environment,在构建SqlSessionFactory的时候,可以指定envonment

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);

如果不指定的话,就会使用默认的:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);

注意:一个SqlSessionFactory只能选择一个environment

然后我们看到transactionManager,这个MyBatis提供了两种内置的事务管理器,JDBCMANAGED。这里不展开说,一般用JDBC就可以了,而且如果使用Spring集成的话,Spring会使用自己的transactionManager来替换MyBatis的transactionManager

然后就是DataSource了,MyBatis内置了三种:UNPOOLEDPOOLEDJNDI。这三个名字已经很明显了,我就不解释了,那如果想使用第三方的数据库连接池呢,该如何处理,简单的来说,可以自己实现DataSourceFactory,我这里选择了据说是最快的HikariCP:

public class HikariCPDataSourceFactory implements DataSourceFactory {

    private HikariConfig config;

    @Override
    public void setProperties(Properties props) {
        config = new HikariConfig(props);
    }

    @Override
    public DataSource getDataSource() {
        return new HikariDataSource(config);
    }
}

然后修改DataSource配置:

<dataSource type="cn.dubby.mybatis.configuration.datasource.HikariCPDataSourceFactory">
    <property name="driverClassName" value="${driver#|#com.mysql.cj.jdbc.Driver}"/>
    <property name="jdbcUrl" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</dataSource>

mappers

就是在这里配置所有的mapper,有这么几种选择:

<!-- 使用相对路径 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用绝对路径 -->
<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>

2、XxxMapper.xml

mapper是MyBatis真正神奇的地方,这里让我们可以使用几乎原生的sql,但却又可以摆脱重复枯燥的jdbc拼接代码,让我们开始了解他吧,他的可以配置项分为:

  • cache – 在当前namespcae配置一个缓存
  • cache-ref – 从其他namespace引用一个缓存.
  • resultMap – 定义了如何把从数据库中查询出来的数据封装成Java的Object
  • parameterMap – Deprecated! 后续可能会删除,这里不解释
  • sql – 可重复使用的一段SQL
  • insert – 插入
  • update – 更新
  • delete – 删除
  • select – 选择
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值