一些优化
优化数据库配置信息
可以将配置信息单独放入(要手动新建的)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类型 |
BooleanTypeHandler | java.lang.Boolean,boolean | 数据库兼容的 BOOLEAN |
ByteTypeHandler | java.lang.Byte, byte | 数据库兼容的 NUMERIC 或 BYTE |
ShortTypeHandler | java.lang.Short, short | 数据库兼容的 NUMERIC 或 SHORT INTEGER |
IntegerTypeHandler | java.lang.Integer, int | 数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler | java.lang.Long, long | 数据库兼容的 NUMERIC 或 LONG INTEGER |
FloatTypeHandler | java.lang.Float, float | 数据库兼容的 NUMERIC 或 FLOAT |
DoubleTypeHandler | java.lang.Double,double | 数据库兼容的 NUMERIC 或 DOUBLE |
BigDecimalTypeHandler | java.math.BigDecimal | 数据库兼容的 NUMERIC 或 DECIMAL |
StringTypeHandler | java.lang.String | CHAR, VARCHAR |
ClobReaderTypeHandler | java.io.Reader | – |
ClobTypeHandler | java.lang.String | CLOB, LONGVARCHAR |
NStringTypeHandler | java.lang.String | NVARCHAR, NCHAR |
NClobTypeHandler | java.lang.String | NCLOB |
BlobInputStreamTypeHandler | java.io.InputStream | – |
ByteArrayTypeHandler | byte[] | 数据库兼容的字节流类型 |
BlobTypeHandler | byte[] | BLOB, LONGVARBINARY |
DateTypeHandler | java.util.Date | TIMESTAMP |
DateOnlyTypeHandler | java.util.Date | DATE |
TimeOnlyTypeHandler | java.util.Date | TIME |
SqlTimestampTypeHandler | java.sql.Timestamp | TIMESTAMP |
SqlDateTypeHandler | java.sql.Date | DATE |
SqlTimeTypeHandler | java.sql.Time | TIME |
ObjectTypeHandler | Any | OTHER 或未指定类型 |
EnumTypeHandler | Enumeration Type | VARCHAR-任何兼容的字符串类型,存储枚举的名称(而不是索引) |
EnumOrdinalTypeHandler | Enumeration 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的实体类明显是按照数据库来设计的,只有没事找事想增加项目工程量的人才会乱写实体类的成员变量。