MyBatis的XML配置文件是有层次结构的,因为存在约束嘛,这些层次是有顺序的,如果颠倒顺序,MyBatis就会解析出错。来看一看这个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> <!-- 配置 --> <properties></properties><!-- 属性 --> <settings></settings><!-- 设置 --> <typeAliases></typeAliases><!--别名 --> <typeHandlers></typeHandlers><!-- 类型处理器 --> <objectFactory type=""></objectFactory><!-- 对象工厂 --> <plugins></plugins><!-- 插件 --> <environments default=""><!-- 配置环境 --> <environment id=""><!-- 环境变量 --> <transactionManager type=""></transactionManager><!-- 事务管理器 --> <dataSource type=""></dataSource> <!-- 数据源 --> </environment> </environments> <databaseIdProvider type=""></databaseIdProvider><!-- 数据库厂商标识 --> <mappers></mappers> <!-- 映射器 --> </configuration>
ProPerties元素
properties是一个配置属性元素,让我们在配置文件上下文中使用它。
MyBatis提供了3种配置方式
- property子元素
- properties配置文件
- 程序参数传递
property子元素
配置如下:
<properties> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/> <property name="username" value="root"/> <property name="password" value="root"/> </properties>
这样我们就可以在DataSource标签中配置:
<dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource>
properties配置文件
更多时候我们是将这个数据库的配置放在单独的properties文件中,需要在MyBatis配置文件中导入:
<properties resource="jdbc.properties"></properties>
将这个外部的数据库信息配置在一个单独的文件中
程序参数配置
如果为了安全,数据库的用户名和密码都是加密的,我们无法使用这个加密的字符串来连接数据库,这个时候可以使用编码的方式来满足这种场景。
修改一下获得SqlSessionFactory的initSqlSessionFactory方法:
public static SqlSessionFactory initSqlSessionFactory() { InputStream cfgStream=null; Reader cfgReader=null; InputStream proInputStream=null; Reader proReader=null; try { //读入配置文件 cfgStream=Resources.getResourceAsStream("mybatis-config.xml"); cfgReader=new InputStreamReader(cfgStream); //读入属性文件 proInputStream=Resources.getResourceAsStream("jdbc.properties"); proReader=new InputStreamReader(proInputStream); Properties properties=new Properties(); properties.load(proReader); //解密为明文 decode为解密方法 properties.setProperty("username", decode(properties.getProperty("username"))); properties.setProperty("password", decode(properties.getProperty("password"))); synchronized (CLASS_LOCK) { if(sqlSessionFactory==null) { sqlSessionFactory=new SqlSessionFactoryBuilder().build(cfgReader,properties); } } }catch (Exception e) { e.printStackTrace(); } }
主要是我们调用SqlSessionFactoryBuilder类的一个重载build(Reader,Properties)
优先级问题
通过方法参数传递的方式优先级最高;外部配置文件的方式次之;properties元素的方式最先被加载,但是会被比它优先级高的覆盖,所以优先级最低
(优先级:参数传递>外部配置文件>properties元素,反之,就是它们加载的先后顺序)。
注意:
1.不要混用这些方式
2.首选外部配置的方式
3.如果有对于用户名或密码加密的场景,就是用方法参数的方式。
其是在这里配置的properties标签在与spring整合之后,就会消失了,我们都将其配置在applicationContext.xml文件中。
设置
设置(settings)在MyBatis总是最为复杂的配置,同时也是最为重要的配置,它会改变MyBatis运行时的行为。即使不配置setting,MyBatis也会工作,因为其属性都有默认值的存在,在这里不显示其配置了,配置不需要修改太多,一般来说我们只会的修改一小部分就可以了。
别名
别名(typeAliases)是一个指代的名称,因为我们遇到的全限定名都太长了,所以希望使用一个简短的名称代替, 这个名称可以再MyBatis的整个上下文中使用。在MyBatis中分为系统定义别名和自定义别名两类,注意:在MyBatis中别名是不分大小写的。一个typeAliases实例在解析配置文件的时候生成,然后长期保存在Configuration对象中,但我们使用时,再把它拿出来,这样就没必要运行时再生成它的实例了。
系统别名
MyBatis中默认了一些经常使用的类型别名,例如:数值,字符串,日期和集合等,我们在MyBatis中直接使用即可,在使用的时候不要重复定义把它给覆盖了。基本上都是将类型的首字母小写了,如:
别名 | 映射的类型 | 支持数组 |
string | String | 否 |
short | Short | 是 |
integer | Integer | 是 |
float | Float | 是 |
boolean | Boolean | 是 |
date | Date | 是 |
list | List | 否 |
arraylist | ArrayList | 否 |
map | Map | 否 |
hashmap | HashMap | 否 |
object | Object | 是 |
我们还可以在 MyBatis的源码 org.apache.ibatis.type.TypeAliasRegistry 可以看到更为详细的信息
自定义别名
系统定义的别名往往是不够的,因为不同的应用存在各种不同的需求,所以MyBatis可以自定义别名。我们使用typeAliases配置别名:
<!-- 配置别名 --> <typeAliases> <typeAlias alias="user" type="cn.lynu.model.User"/> </typeAliases>
alias属性就是在取别名,type指定其全限定名。如果我们没有使用ailas属性来设置别名,会有默认别名:类名首字母小写
如果我们懂得POJO类比较多,MyBatis可以允许我们通过自动扫描的形式自定义别名:
<!-- 扫描别名 --> <typeAliases> <package name="cn.lynu.model"/> </typeAliases>
这样就可以自动扫描 cn.lynu.model 包下的POJO了,默认每个POJO的别名是将类名的首字母小写,当然我们也可以自己指定别名,使用@Alias注解标识在POJO类上:
类型处理器
类型处理器(typeHandler)是MyBatis在于预处理语句(PreparedStatement)中设置一个参数,或者从结果集中(result)中取出值时,都会用注册了的typeHandler进行处理。typeHandler常用的配置为java类型(javaType),JDBC类型(jdbcType)。所以说,typeHandler的作用就是将参数从javaType转换为javaType,或者是从数据库取结果的时候把jdbcType转换为javaType。
由于数据库厂商的不同,不同的厂商之间的设置的参数也可能会有所不同,所以MyBatis允许我们自定义typeHandler,我们往往需要对数据进行特殊的处理,这些都需要使用到自定义的typeHandler,例如:在使用枚举的时候常常需要使用自定义的typeHandler进行数据的转换。
同别名一样,类型处理器也有系统定义和用户定义两种
系统定义的typeHandler
来看几个系统定义的typeHandler:
类型处理器 | Java类型 | JDBC类型 |
BooleanTypeHandler | java.lang.Boolean.boolean | 数据库兼容的BOOLEAN |
IntegerTypeHandler | java.lang.Integer.integer | 数据库兼容的INTEGER |
LongTypeHandler | java.lang.Long.long | 数据库兼容的Long |
StringTypeHandler | java.lang.String | CHAR, VARCHAR |
ClobTypeHandler | java.lang.String | CLOB,LONGVARCHAR |
ByteArrayTypeHandler | byte[] | 数据库兼容的字节流类型 |
BlobTypeHandler | byte[] | BLOB,LONGVARBINARY |
DateOnlyTypeHandler | java.util.Date | DATE |
TimeOnlyTypeHandler | java.util.Date | TIME |
SqlTimestampTypeHandler | java.sql.Timestamp | TIMESTAMP |
SqlDateTypeHandler | java.sql.Date | Date |
SqlTimeTypeHandler | java.sql.Tome | TIME |
EnumTypeHandler | Enum | VARCHAR或任何兼容的的字符串类型,存储的是枚举的名称(而不是索引)默认的枚举处理类 |
EnumOrdinalTypeHandler | Enum | 任何兼容的数值类型,存储的是枚举的索引(而不是名称) |
需要注意:
- 数值类型的精度,数据库的int,double这些类型和java的精度,长度都是不一样的。
- 时间精度,取数据到日用 DateOnlyTypeHandler 即可,用到精度到秒的用 SqlTimestampTypeHandler 。
自定义typeHandler
一般而言,Mybatis系统定义的typeHandler已经能应付大部分的场景了,但不排除不够用的时候,我们自定义的typeHandler需要明确要解决什么样的问题?现有的typeHandler是否够我们使用?
接下来我们模仿StringTypeHandler来编写一个我们自己的处理String类型的
步骤1:首先配置XML文件
<typeHandlers> <typeHandler handler="cn.lynu.typeHandler.MyStringTypeHandler" javaType="string" jdbcType="VARCHAR"/> </typeHandlers>
handler指定我们自定义typeHandler的全限定名,并指明 javaType 是String,JDBCType是VARCHAR
步骤2:编写自定义TypeHandler
自定义TypeHandler的要求就是必须实现接口:org.apache.ibatis.type.TypeHandler,也可以继承BaseTypeHandler ,因为BaseTypeHandler实现了这个接口,这里我们先使用继承接口的方式吧
package cn.lynu.typeHandler; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedJdbcTypes; import org.apache.ibatis.type.MappedTypes; import org.apache.ibatis.type.TypeHandler; @MappedTypes({String.class}) @MappedJdbcTypes({JdbcType.VARCHAR}) public class MyStringTypeHandler implements TypeHandler<String>{ @Override public String getResult(ResultSet rs, String value) throws SQLException { System.out.println("使用的是result和value"); return rs.getString(value); } @Override public String getResult(ResultSet rs, int index) throws SQLException { System.out.println("使用的是result和index"); return rs.getString(index); } @Override public String getResult(CallableStatement cs, int index) throws SQLException { return cs.getString(index); } @Override public void setParameter(PreparedStatement ps, int index, String value, JdbcType jt) throws SQLException { ps.setString(index, value); } }
可以看到代码中有三个重载的getResult方法,分别根据Result和value,Result和index,存储过程callableStatement来获得值,setParameter方法使用预编译PreparedStatement来设置值。
@MappedTypes 定义的是JavaType类型,可以指定哪些Java类型可以被处理
@MappedJdbcTypes 定义的JdbcType类型,其值可以从org.apache.ibatis.type.JdbcType 的枚举值中获得
步骤3:在Mapper的映射XML文件使用
<resultMap type="user" id="userMap"> <!-- 定义结果集 --> <id column="id" jdbcType="INTEGER" javaType="int" property="id" /> <result column="username" property="userName" typeHandler="cn.lynu.typeHandler.MyStringTypeHandler" /> </resultMap>
这里处理username就是使用的我们自定义的TypeHandler
public User findUserById2(int id)throws Exception;
<select id="findUserById2" resultMap="userMap" parameterType="int"> select id,username from user where id = #{id} </select>
经测试可以正常运行
枚举类型typeHandler
MyBatis有两个用于枚举类型的类型处理器,分别为EnumTypeHandler(将枚举的值存入数据库)和EnumOrdinalTypeHandler(将枚举的下标存入数据库)
我们来创建一个性别枚举类:
package cn.lynu.model; public enum Sex { MALE(1,"男"),FEMALE(2,"女"); private int id; private String name; private Sex(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
EnumOrdinalTypeHandler虽然是系统自带的,还是需要在MyBatis的配置文件中配置,如果不配置Mybatis默认使用 EnumTypeHandler 作为枚举类型处理器
<typeHandlers> <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="cn.lynu.model.Sex"/> </typeHandlers>
然后我们在mapper的映射xml中使用:
<resultMap type="user" id="userMap"> <!-- 定义结果集 --> <id column="id" jdbcType="INTEGER" javaType="int" property="id" /> <result column="username" property="userName" typeHandler="cn.lynu.typeHandler.MyStringTypeHandler" /> <result column="sex" property="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/> </resultMap>
添加了一个sex字段,并指明TypeHandler为 org.apache.ibatis.type.EnumOrdinalTypeHandler
<insert id="insertUser" parameterType="user"> insert into user(username,sex) values(#{userName},#{sex}) </insert>
这是一个添加的操作,测试可以正确的先数据库中添加数据,性别字段使用的下标,而使用EnumTypeHandler就是使用枚举的值,就不复述了。
自定义枚举TypeHandler和之前我们自定义的 MyStringTypeHandler 一样的创建和使用方法
对象工厂
当MyBatis在构建一个结果返回的时候,都会使用ObjectFactory(对象工厂)来封装POJO,大部分情况下,我们不需要自己去配置ObjectFactory,使用默认即可。
插件
插件较为复杂,使用是要小心,因为插件会覆盖一些MyBatis内部对象的行为,我们在后面讨论。
environments配置环境
先来看一个环境配置:
<!-- 和spring整合之后会废除environments --> <environments default="development"> <environment id="development"> <!-- 使用JDBC事务,事务控制有Mybatis控制 --> <transactionManager type="JDBC" /> <!--数据库连接池,由Mybatis创建 --> <dataSource type="POOLED"> <!--这里使用前面我们说的外部jdbc属性文件的方式--> <property name="driver" value="${jdbc.driverClass}" /> <property name="url" value="${jdbc.jdbcUrl}" /> <property name="username" value="${jdbc.user}" /> <property name="password" value="${jdbc.password}" /> </dataSource> </environment> </environments>
environments有一个defaule属性,标明在默认情况下,我们使用哪个数据源配置。
environment标签是配置一个数据源的开始,属性id是设置这个数据源的标志,environments的default使用的就是个值
transactionManager 配置的是事务,其实type的值可以为:
1.JDBC,采用JDBC方式管理事务
2.MANAGED,采用容器的方式管理事务,JNDI数据源中使用
3.自定义,可以由使用者自定义数据库事务的管理
dataSource标签是配置数据源的各项属性,type属性是提供我们对数据库的连接方式,可选择值为:
1.UNPOOLED,非连接池的方式,由 org.apache.ibatis.datasource.unpooled.UnpooledDataSource.class实现
2.POOLED,连接池的方式,由 org.apache.ibatis.datasource.pooled.PooledDataSource 实现
3.JNDI,JNDI数据源
4.自定义数据源
其中,配置property元素,就是在定义数据库的各项参数(四大参数等等)
大部分这些配置我们都与Spring 框架来控制,这以后再说。
databaseIdProvider数据库厂商标识
在相同的数据库中使用这个毫无意义,在实际中使用很少,因为很少有项目使用不同的数据库系统。MyBatis可以设置一个数据库厂商标识,用于将指定的SQL到对应的数据库中使用。
默认的系统规则
<databaseIdProvider type="DB_VENDOR"> <!-- 配置数据库标识的时候注意name值的大小写 --> <property name="SQL Server" value="sqlserver"/> <property name="MySQL" value="mysql"/> <property name="DB2" value="db2"/> <property name="Oracle" value="oracle"/> </databaseIdProvider>
type="DB_VENDOR" 就是使用默认的数据库厂商标识,但是需要注意property的name属性值,是区分大小写的
我们可以使用:
System.out.println(sqlSessionFactory.getConfiguration().getDatabaseId());
将现在使用的数据库打印出来,输出的就是value值,没有配置databaseId或没有根据name值匹配上,就会返回null。
将Mapper的XML文件修改一下,说明该SQL由哪个数据库执行:
<select id="findUserById2" resultType="user" parameterType="int" databaseId="mysql"> select id,username,birthday from user where id = #{id} </select>
就是添加一个databaseId属性,其值就是property的value值。这条SQL就会在MySQL数据库中使用。如果有两条相同的SQL,一条使用databaseID数据库标识,一条不使用,后者就会舍弃。
引入映射器方法
映射器是MyBatis最复杂,最核心的东西,我们之后再来研究,这里只是说明如何引入映射器。引入映射器的方法有很多,一般有以下几种:
1.使用文件路径(这种方式可以让映射文件放在其他包下)
<mappers> <mapper resource="cn/lynu/mapper/userMapper.xml"/> </mappers>
2.用包名引入映射器(这样可以一次导入一个包下的mapper的XML,但是需要将sql映射文件和接口发在同一个包下。建议写法:在config或Maven的resource下建立一个与接口同名的包,将sql映射文件放入其中)
<mappers> <package name="cn.lynu.mapper" /> </mappers>
3.用类注册引入映射器(如果在接口中使用注解的方式来映射接口,推荐使用这种引入方法;如果还是使用xml映射文件则要求映射文件和接口放在同一个的包下)
<mappers> <mapper class="cn.lynu.mapper.UserMapper"/> </mappers>
我们需要根据实际情况恰当的选择合适的引入方法