Mybatis学习-配置、作用域和生命周期

核心配置文件:Mybatis-config.xml
Mybatis的配置文件包含了会深深影响Mybatis行为的设置和属性信息

配置(configuration)

在mybatis-config.xml文件中标签都有规定的顺序,需要按照以下顺序添加
properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?

属性(properties)

我们可以通过properties来实现引用文件。Mybatis的一些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在properties元素的子元素中设置

  1. 编写一个配置文件(db.properties)
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/learn?serverTimezone=GMT&useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456789
  1. 在mybatis-config.xml中引入
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "//mybatis.org/dtd/mybatis-3-config.dtd">

<!--核心配置文件-->
<configuration>

    <!--引入外部配置文件-->
    <properties resource="db.properties" />
    
    <!--也可以配置文件中写一部分,标签中写一部分-->
    <!--优先使用外部配置文件中的配置-->
    <properties resource="db.properties">
    	<property name="username" value="root"/>
        <property name="pwd" value="123456789"/>
    </properties>

    <environments default="test">
       
        <environment id="test">
            <transactionManager type="JDBC"/>
            <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>

    <!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>
  1. 测试
@Test
public void test() {
    //获取SQLSession对象
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    try {

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList();

        for (User user : userList) {
           System.out.println(user);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        //关闭SQLSession
        sqlSession.close();
    }
}

Mybatis会先读取properties元素体内的指定属性,再根据resource属性读取类路径下属性文件。所以外部配置文件中编写的配置会覆盖内部标签编写的配置,外部配置文件拥有更高优先级

设置(setting)

这是Mybatis中极为重要的调整设置,他们会改变Mybatis的运行时行为

更多设置请参考://mybatis.org/mybatis-3/zh/configuration.html#settings

环境配置(environments)

Mybatis可以配置成适应多种环境
尽管可以配置多个环境,但每个SQLSessionFactory实例只能选择一种环境
Mybatis默认的事务管理器就是JDBC,数据源是POOLED

<environments default="test">	<!--需要使用的环境-->
    
    <!--环境一-->
    <environment id="development">		<!--id:每套environment的唯一标识-->
        <transactionManager type="JDBC"/>	<!--事务管理器的配置-->
        <dataSource type="POOLED">			<!--数据源的配置-->
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/learn?serverTimezone=GMT&amp;useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
            <property name="username" value="root"/>
            <property name="password" value="123456789"/>
        </dataSource>
    </environment>

    <!--环境二-->
    <environment id="test">
        <transactionManager type="JDBC"/>
        <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>

类型别名(typeAliases)

类型别名为Java类型设置一个短的名字,它仅用于XML配置,存在的意义仅在于用来减少类完全限定名的冗余

<typeAliases>
    <typeAlias alias="User" type="com.luoqing.model.User"/>
</typeAliases>

也可以指定一个包名,Mybatis会在包名下搜索需要的JavaBean

<typeAliases>
    <package name="com.luoqing.model" />
</typeAliases>

每一个在包名下的JavaBean,在没有注解的情况下,会使用Bean首字母小写的非限定类名来作为它的别名
如果有注解则其别名为其注解值

//这个类的别名为user
@Alias("user")
public class User {
    ……
}

在实体类比较少的时候,使用第一种方式。如果实体类十分多,建议使用第二种

类型处理器(typeHandlers)

Mybatis使用typeHandlers将数据库中字段的类型转换为java类中属性的类型,或将java类中属性的类型转换为数据库中字段的类型

通过Configuration对象获取

@Test
public void myTest() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    TypeHandlerRegistry typeHandlerRegistry = sqlSession.getConfiguration().getTypeHandlerRegistry();
    Collection<TypeHandler<?>> list = typeHandlerRegistry.getTypeHandlers();
    System.out.println(list.size());
    for (TypeHandler<?> typeHandler : list) {
        System.out.println(typeHandler.getClass().getName());
    }

    sqlSession.close();

}

执行结果如下。以下所有的处理器类位于mybatis-X.X.X.jar/org/apache/ibatis/type中

40
org.apache.ibatis.type.InstantTypeHandler
org.apache.ibatis.type.MonthTypeHandler
org.apache.ibatis.type.JapaneseDateTypeHandler
org.apache.ibatis.type.UnknownTypeHandler
org.apache.ibatis.type.DateTypeHandler
org.apache.ibatis.type.CharacterTypeHandler
org.apache.ibatis.type.BigIntegerTypeHandler
org.apache.ibatis.type.SqlxmlTypeHandler
org.apache.ibatis.type.LocalDateTimeTypeHandler
org.apache.ibatis.type.ArrayTypeHandler
org.apache.ibatis.type.YearTypeHandler
org.apache.ibatis.type.FloatTypeHandler
org.apache.ibatis.type.NStringTypeHandler
org.apache.ibatis.type.BooleanTypeHandler
org.apache.ibatis.type.ByteTypeHandler
org.apache.ibatis.type.ClobTypeHandler
org.apache.ibatis.type.BigDecimalTypeHandler
org.apache.ibatis.type.ByteArrayTypeHandler
org.apache.ibatis.type.BlobTypeHandler
org.apache.ibatis.type.DateOnlyTypeHandler
org.apache.ibatis.type.YearMonthTypeHandler
org.apache.ibatis.type.SqlDateTypeHandler
org.apache.ibatis.type.OffsetTimeTypeHandler
org.apache.ibatis.type.LongTypeHandler
org.apache.ibatis.type.TimeOnlyTypeHandler
org.apache.ibatis.type.ZonedDateTimeTypeHandler
org.apache.ibatis.type.BlobInputStreamTypeHandler
org.apache.ibatis.type.LocalDateTypeHandler
org.apache.ibatis.type.IntegerTypeHandler
org.apache.ibatis.type.StringTypeHandler
org.apache.ibatis.type.BlobByteObjectArrayTypeHandler
org.apache.ibatis.type.ShortTypeHandler
org.apache.ibatis.type.NClobTypeHandler
org.apache.ibatis.type.LocalTimeTypeHandler
org.apache.ibatis.type.ClobReaderTypeHandler
org.apache.ibatis.type.SqlTimestampTypeHandler
org.apache.ibatis.type.SqlTimeTypeHandler
org.apache.ibatis.type.ByteObjectArrayTypeHandler
org.apache.ibatis.type.OffsetDateTimeTypeHandler
org.apache.ibatis.type.DoubleTypeHandler

Mybatis已经为我们写好了40个类型处理器
其中DateTypeHandlers的源码如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.type;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;

public class DateTypeHandler extends BaseTypeHandler<Date> {
    public DateTypeHandler() {
    }

    /**
    	获取到对应的java类型的对象,将其转换为jdbc类型
    */
    public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
        ps.setTimestamp(i, new Timestamp(parameter.getTime()));
    }

    /**
    	获取到对应列的jdbc类型的对象,将其转换为java类型
    */
    public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Timestamp sqlTimestamp = rs.getTimestamp(columnName);
        return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;
    }

    /**
    	根据索引获取到对应的数据的jdbc类型将其转换为java类型
    */
    public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Timestamp sqlTimestamp = rs.getTimestamp(columnIndex);
        return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;
    }

    /**
    	这个方法用在存储过程中。将jdbc类型转换为java类型
    */
    public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Timestamp sqlTimestamp = cs.getTimestamp(columnIndex);
        return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;
    }
}

它的父类BaseTypeHandler<T>

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.type;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.executor.result.ResultMapException;
import org.apache.ibatis.session.Configuration;

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
    /** @deprecated */
    @Deprecated
    protected Configuration configuration;

    public BaseTypeHandler() {
    }

    /** @Deprecated表示此方法或者类不推荐使用,但是不代表不能用*/
    /** @deprecated */
    @Deprecated
    public void setConfiguration(Configuration c) {
        this.configuration = c;
    }

    
    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {
            if (jdbcType == null) {
                throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
            }

            try {
                /** jdbcType是一个枚举类型 */
                /** 将对应的jdbc类型设置为null */
                ps.setNull(i, jdbcType.TYPE_CODE);
            } catch (SQLException var7) {
                throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. Cause: " + var7, var7);
            }
        } else {
            try {
                this.setNonNullParameter(ps, i, parameter, jdbcType);
            } catch (Exception var6) {
                throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . Try setting a different JdbcType for this parameter or a different configuration property. Cause: " + var6, var6);
            }
        }

    }

    public T getResult(ResultSet rs, String columnName) throws SQLException {
        try {
            return this.getNullableResult(rs, columnName);
        } catch (Exception var4) {
            throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + var4, var4);
        }
    }

    public T getResult(ResultSet rs, int columnIndex) throws SQLException {
        try {
            return this.getNullableResult(rs, columnIndex);
        } catch (Exception var4) {
            throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set.  Cause: " + var4, var4);
        }
    }

    public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
        try {
            return this.getNullableResult(cs, columnIndex);
        } catch (Exception var4) {
            throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement.  Cause: " + var4, var4);
        }
    }

    //定义了四个抽象方法,并在上面接口方法的实现中使用了它们
    //具体的实现交给子类去完成
    public abstract void setNonNullParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;

    public abstract T getNullableResult(ResultSet var1, String var2) throws SQLException;

    public abstract T getNullableResult(ResultSet var1, int var2) throws SQLException;

    public abstract T getNullableResult(CallableStatement var1, int var2) throws SQLException;
}

BaseTypeHandler是一个抽象类,也是类型处理器的基类。它作为TypeHandler接口的初步实现,实现了TypeHandler的四个方法;还另外定义了四个抽象方法,也就是DateTypeHandler中的四个方法。系统定义的40个TypeHandler方法都继承自BaseTypeHandler

实现的接口TypeHandler<T>

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.type;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public interface TypeHandler<T> {
    void setParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;

    T getResult(ResultSet var1, String var2) throws SQLException;

    T getResult(ResultSet var1, int var2) throws SQLException;

    T getResult(CallableStatement var1, int var2) throws SQLException;
}

如果我们要自定义typeHandler时:

  • 继承BaseTypeHandler类
  • 实现TypeHandler接口

一般不需要自定义,使用默认的就够了

映射器(mappers)

MapperRegistry:注册绑定我们的Mapper文件

方式一:

<!-- 使用相对于类路径的资源引用,在resource文件夹中 -->
<mappers>
  <mapper resource="com/luoqing/dao/UserMapper.xml"/>
</mappers>

方式二:使用class文件绑定注册

<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="com.luoqing.dao.UserMapper.xml"/>
</mappers>

注意:

  • 接口和它的Mapper配置文件必须同名
  • 接口和它的Mapper配置文件必须在同一个包下

方式三:

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="com.luoqing.dao"/>
</mappers>

注意:

  • 接口和它的Mapper配置文件必须同名
  • 接口和它的Mapper配置文件必须在同一个包下

方式四:使用映射器接口实现类的完全限定名(因为基本不使用,在此不多赘述,如要学习请移步官方文档)
//mybatis.org/mybatis-3/zh/configuration.html#mappers

作用域(Scope)和生命周期

不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题

SqlSessionFactoryBuilder

一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次。因此 SqlSessionFactory 的最佳作用域是应用作用域。最简单的就是使用单例模式或者静态单例模式。

SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。下面的示例就是一个确保 SqlSession 关闭的标准模式:

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

在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。

Tags: