MyBatis学习总结(三)

一些优化

优化数据库配置信息

可以将配置信息单独放入(要手动新建的)db. properties文件中, 然后再从conf.xml动态引入。

db. properties文件内容是由key-value组成的:

driver:com.mysql.cj.jdbc.Driver
url:jdbc:mysql://localhost:3306/person?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
username:root
password:password

conf引用方式,是在最外层configuration下添加properties标签:

<properties resource="db.properties"/>

有一个小小问题是,最开始的配置信息是直接写在conf.xml文件中的,我将配置信息直接从eclipse的jdbc项目中抄写过来的,idea自动帮我在url的每一个“&”号处添加了“amp;”,但是写在db. properties文件中要删除。

MyBatis全局参数

MyBatis全局参数就是一些MyBatis具体运行时候的参数设置,一般要慎用,有以下这些参数:

参数简介有效值
cacheEnabled在全局范围内,启用或禁用缓存true(默认)、false
lazyLoadingEnabled在全局范围内启用或禁用延迟加载。当禁用时,所有相关联的对象都将立即加载(热加载)。true(默认)、false
aggressiveLazyLoading启用时,有延迟加载属性的对象,在被调用时将会完全加载所有属性(立即加载)。否则,每一个属性都将按需加载(即延迟加载)。true(默认)、false
multipleResultSetsEnabled允许或禁止执行一条单独的SQL语句后返回多条结果(结果集);需要驱动程序的支持true(默认)、false
autoMappingBehavior指定数据表字段和对象属性的映射方式。NONE:禁止自动映射,只允许手工配置的映射PARTIAL:只会自动映射简单的、没有嵌套的结果FULL:自动映射任何结果(包含嵌套等)NONE、PARTIAL(默认)、FULL
defaultExecutorType指定默认的执行器。SIMPLE:普通的执行器。REUSE:可以重复使用prepared statements语句。BATCH:可以重复执行语句和批量更新。SIMPLE(默认)、REUSE、BATCH
defaultStatementTimeout设置驱动器等待数据库回应的最长时间以秒为单位的,任意正整数。无默认值
safeRowBoundsEnabled允许或禁止使用嵌套的语句true、false(默认)
mapUnderscoreToCamelCase当在数据表中遇到有下划线的字段时,自动映射到相应驼峰式形式的Java属性名。例如,会自动将数据表中的stu_no字段,映射到POJO类的stuNo属性。true、false(默认)
lazyLoadTriggerMethods指定触发延迟加载的对象的方法equals、clone、hashCode、toString

如果有需要,在conf.xml文件中设置:

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

例如:

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

设置别名

别名就是给一些很长很复杂的值起一个简单的别名。别名是忽略大小写的,有以下两种方式:

设置单个别名

    <typeAliases>
       <typeAlias type="shopkeeper.entity.Person" alias="person"/>
    </typeAliases>

批量设置别名

    <typeAliases>
        <package name="shopkeeper.entity"/>
    </typeAliases>

除了我们可以自定义别名之外,MyBatis还有一些内置的别名:

类型转换器

java类型和数据库(如MySQL)的类型并不是完全匹配,例如String和varchar,但是MyBatis可以自动将其相互转化,这里就是用到了类型转换器。

MyBatis自带一些常见的类型处理器:

类型处理器Java类型JDBC类型
BooleanTypeHandlerjava.lang.Boolean,boolean数据库兼容的 BOOLEAN
ByteTypeHandlerjava.lang.Byte, byte数据库兼容的 NUMERIC 或 BYTE
ShortTypeHandlerjava.lang.Short, short数据库兼容的 NUMERIC 或 SHORT INTEGER
IntegerTypeHandlerjava.lang.Integer, int数据库兼容的 NUMERIC 或 INTEGER
LongTypeHandlerjava.lang.Long, long数据库兼容的 NUMERIC 或 LONG INTEGER
FloatTypeHandlerjava.lang.Float, float数据库兼容的 NUMERIC 或 FLOAT
DoubleTypeHandlerjava.lang.Double,double数据库兼容的 NUMERIC 或 DOUBLE
BigDecimalTypeHandlerjava.math.BigDecimal数据库兼容的 NUMERIC 或 DECIMAL
StringTypeHandlerjava.lang.StringCHAR, VARCHAR
ClobReaderTypeHandlerjava.io.Reader
ClobTypeHandlerjava.lang.StringCLOB, LONGVARCHAR
NStringTypeHandlerjava.lang.StringNVARCHAR, NCHAR
NClobTypeHandlerjava.lang.StringNCLOB
BlobInputStreamTypeHandlerjava.io.InputStream
ByteArrayTypeHandlerbyte[]数据库兼容的字节流类型
BlobTypeHandlerbyte[]BLOB, LONGVARBINARY
DateTypeHandlerjava.util.DateTIMESTAMP
DateOnlyTypeHandlerjava.util.DateDATE
TimeOnlyTypeHandlerjava.util.DateTIME
SqlTimestampTypeHandlerjava.sql.TimestampTIMESTAMP
SqlDateTypeHandlerjava.sql.DateDATE
SqlTimeTypeHandlerjava.sql.TimeTIME
ObjectTypeHandlerAnyOTHER 或未指定类型
EnumTypeHandlerEnumeration TypeVARCHAR-任何兼容的字符串类型,存储枚举的名称(而不是索引)
EnumOrdinalTypeHandlerEnumeration Type任何兼容的 NUMERIC 或 DOUBLE 类型,存储枚举的索引(而不是名称)

我们还可以自定义一些MyBatis类型处理器(虽然设计数据库时就应该好好考虑让类型匹配):

例如我们要将java的Boolean对应到数据库中的Integer,其中true=1,false=0,步骤如下:

创建转换器

需要实现TypeHandler接口,但是通过阅读源码发现,此接口有一个实现类BaseTypeHandler,因此要实现转换器有2种选择:

1.实现接口TypeHandler接口

2.继承BaseTypeHandler

继承接口的实现类要做的工作量肯定比直接实现接口要简单些,所以需要新建一个转换器类继承BaseTypeHandler:

public class BooleanAndIntConverter extends BaseTypeHandler<Boolean> {
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, Boolean aBoolean, JdbcType jdbcType) throws SQLException {

        if (aBoolean)
        {
            preparedStatement.setInt(i,1);
        }
        else
        {
            preparedStatement.setInt(i,0);
        }
    }

    @Override
    public Boolean getNullableResult(ResultSet resultSet, String s) throws SQLException {
        int sexNum=resultSet.getInt(s);
        return sexNum==1?true:false;
    }

    @Override
    public Boolean getNullableResult(ResultSet resultSet, int i) throws SQLException {
        int sexNum=resultSet.getInt(i);
        return sexNum==1?true:false;
    }

    @Override
    public Boolean getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        int sexNum=callableStatement.getInt(i);
        return sexNum==1?true:false;
    }


}

首先继承的BaseTypeHandler类有一个泛型,这个泛型是java的类型。继承写明了泛型之后,需要实现的方法的java那一段的参数就会自动根据泛型进行格式转化。例如这四个方法中的Boolean。

如果不写明泛型,那么这些值的类型就是object:

@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Object o, JdbcType jdbcType) throws SQLException {

}

@Override
public Object getNullableResult(ResultSet resultSet, String s) throws SQLException {
return null;
}

@Override
public Object getNullableResult(ResultSet resultSet, int i) throws SQLException {
return null;
}

@Override
public Object getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return null;
}

四个方法由set由get,其中数据的方向是:

get: DB-> java
set:java-> DB

在唯一的一个set方法中,参数PreparedStatement preparedStatement可以理解为数据库中的一个元组;int i是PreparedStatement的操作参数位置(一个元组某个属性的列号);Object o是java那一端的值的对象;JdbcType jdbcType是JDBC中对应的类型,其应该是由preparedStatement的set方法来决定的,例如:

@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Boolean aBoolean, JdbcType jdbcType) throws SQLException {

if (aBoolean)
{
preparedStatement.setInt(i,1);
}
else
{
preparedStatement.setInt(i,0);
}
}

三个get方法是三种从数据库取出值的方式,取出值之后再经过自定义的类型的转化作为返回值传给java:

@Override
public Boolean getNullableResult(ResultSet resultSet, String s) throws SQLException {
int sexNum=resultSet.getInt(s);
return sexNum==1?true:false;
}

@Override
public Boolean getNullableResult(ResultSet resultSet, int i) throws SQLException {
int sexNum=resultSet.getInt(i);
return sexNum==1?true:false;
}

@Override
public Boolean getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
int sexNum=callableStatement.getInt(i);
return sexNum==1?true:false;
}

在conf.xml文件中配置

在typeHandlers标签下配置:

<typeHandlers>
        <typeHandler handler="shopkeeper.converter.BooleanAndIntConverter" javaType="boolean" jdbcType="INTEGER"/>
    </typeHandlers>

typehandler的handler属性要写全类名,之后还要加上javaType和jdbcTypc属性,注意不知道为什么觉得不错Type要全大写。

测试

在Mapper.xml中增加使用了类型转化器的语句:

<select id="queryPersonByIdWithConverter" resultMap="resultPerson" parameterType="int">
select * from person where id=#{id}
</select>

此时返回值不再是resultType,而是resultMap了。如果类中属性和表中的字段类型能够合理识别(String-varchar) ,则可以使用resultType,否则使用resultMap;如果类中属性名和表中的字段名能够合理识别,则可以使用resultType,否则使用resultMap。

resultMap是需要自己定义的,目的是将java和数据库的值用类型转换器一一映射过去:

<resultMap id="resultPerson" type="person">
    <id property="id" column="id"/>
    <result property="name"  column="name"/>
    <result property="age" column="age"/>
    <result property="sex" column="sex" javaType="boolean" jdbcType="INTEGER"/>
</resultMap>

resultMap的id属性值就是标识它的东西,type是返回值在java当中的类型,这里已经用了别名了所以只写了person。resultMap下的子标签根据主键和非主键由分为id和result,其中property是java当中的成员变量名,column是数据库的列名。如果某一个元素需要使用自定义的类型转换器,还需要再这个标签内加上javaType和jdbcType。

测试类中用到是接口代理的方式,没什么区别。

上面这个例子是返回值需要使用类型转换器,下面这个例子是当输入的参数需要使用类型转换器的情况:

<insert id="addPersonWithConverter" parameterType="person">
insert into person (age,name,id,sex) values (#{age},#{name},#{id},#{sex,javaType=boolean,jdbcType=INTEGER})
</insert>

可以看见需要转换类型的sex,后面加上了javaType和jdbcType。

resultMap的功能

在使用类型转换器的时候已经发现了使用resultMap可以实现类型转换,但是还提到当“如果类中属性名和表中的字段名能够合理识别,则可以使用resultType,否则使用resultMap”时也需要使用resultMap。

在定义java当中的实体类时,所有成员变量都必须和数据库的列名能够一一对应(数据库不区分大小写),否则将会无法匹配,例如这个语句:

<select id="queryPersonById" resultType="person" parameterType="int">
select * from person where id=#{id}
</select>

现在Java中有一个成员变量是id,数据库列名中也有id,那么它们是可以匹配的。但是如果将Java中的id改为num,那么就无法匹配了(返回默认值)。

在resultMap中需要修改property:

<resultMap id="resultPerson" type="person">
    <id property="num" column="id"/>
    <result property="name"  column="name"/>
    <result property="age" column="age"/>
    <result property="sex" column="sex" javaType="boolean" jdbcType="INTEGER"/>
</resultMap>

但是这个情况简直多次一举,因为java的实体类明显是按照数据库来设计的,只有没事找事想增加项目工程量的人才会乱写实体类的成员变量。

发表评论