MyBatis注解
使用注解的方式编写sql语句
我们之前编写利用MyBatis编写sql语句时,都是把sql写在Mapper映射文件中的,每一个接口都对应了一个映射文件,在主配置文件中记录该映射文件,在程序运行时就可以利用MyBatis创建该映射文件对应的接口的代理对象,从而映射文件中每一条sql语句都有对应的方法执行。
MyBatis除了利用映射文件编写sql语句外,还可以利用注解来在接口中编写sql语句,以及其他配置信息。
MyBatis中常见的一些注解
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用
注解的使用
接下来我将对上面每个注解的使用一一讲解:
1.@Insert注解:
这个注解代替了<insert>insert into table values(column)</insert>
标签,该注解只能写在方法前,用于执行新增:
@Insert("insert into table values(#{value})")
public int insert(Student stu);
那么调用方法时执行sql语句就是注解中的sql语句了
2.@Update注解:
这个注解代替了<update>update table set column = #{value} where id = #{value}</update>
标签,该注解只能写在方法前,用于执行更新:
@Update("update table set column = #{value} where id = #{value}")
public int update(Student stu);
那么调用方法时执行sql语句就是注解中的sql语句了
3.@Delete注解:
这个注解代替了<delete>delete from table where id = #{value}</delete>
标签,该注解只能写在方法前,用于执行删除:
@Delete("delete from table where id = #{id}")
public int delete(int id);
那么调用方法时执行sql语句就是注解中的sql语句了
4.@Select、@Results、@Result、@One、@Many
以上三个增删改是mybatis中注解中最简单的三个了,接下来的查询会涉及到各种映射问题,所以我把这几个注解一起介绍:
(1)@Select的简单使用:
这个注解代替了<select>select * from table</select>
标签,该注解只能写在方法前,用于执行查询数据:
@Select("select * from table")
public Student findAll();
查询有点特殊,他不同于其他的sql语句只返回受影响的行数,他得到的是一系列的数据集合,所以我们要将这些数据集合封装成一个个对象才可以。
上面的若是要执行成功得有一个前提,就是数据库的列名要和实体类中的属性名一一对应,众所周知MyBatis是利用反射获根据数据表列名取实体类的属性名进行填值的,若属性名与数据表类名不一致就会填值失败,所以属性名要与数据库类名一一对应。
像上面我的最后一个列名与属性名不一致就会出问题,我们来看看下面的解决方案。
(2)@Results、@Result注解与@Select结合使用
@Result注解代替了:
<resultMap type="Student" id="studentMap">
<id property="studentNo" column="studentNo" />
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="age" column="age"/>
<result property="phone" column="phone"/>
<association property="grade" column="gradeId" select="com.ssm.dao.GradeDao.findByGId"></association>
</resultMap>
其实上面的id标签与result标签都是多余的可有可无的,只是为了能够更好的演示注解实现效果才加上去的。假设数据库字段studentNo与实体类属性名不符合(实体类属性名:id)那么就可以这样写:
<id property="id" column="studentNo" />
注解则是:
@Result(id = true, column = "studentNo", property = "id")
完整的代码:
@Results(id = "blackMap", value = {
@Result(id = true, column = "studentNo", property = "studentNo"),
@Result(column = "name", property = "name"),
@Result(column = "sex", property = "sex"),
@Result(column = "age", property = "age"),
@Result(column = "phone", property = "phone"),
@Result(column = "gradeId", property = "grade",
one = @One(select = "com.ssm.dao.GradeDao.findByGId",
fetchType = FetchType.LAZY)
)
})
@Select("select * from student")
@ResultMap("studentMap")
public Student findAll();
看到上面这一大块先不要慌,我们逐一分析:
我们先看@Results:
这个注解只能写在方法前
@Target({ElementType.METHOD})
public @interface Results {
String id() default "";
Result[] value() default {};
}
这是源码,他的@Target很清楚的标注了这个注解只能写在方法前:ElementType.METHOD
里面还可以写两个属性:一个是id,另一个是value。
id:这个id就是这个映射的唯一标识
value:可以看到他是Result类型数组,从源码中可以看到这个Result也是一个注解,我们就可以知道这是一个可以存放注解的数组
所以就有了以上的代码,在value属性中嵌套了很多@Result注解
现在我们再来分析@Result注解
@Result注解
我们先看源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})//不可以写在与任何类有关的结构前
public @interface Result {
boolean id() default false;
String column() default "";
String property() default "";
Class<?> javaType() default void.class;
JdbcType jdbcType() default JdbcType.UNDEFINED;
Class<? extends TypeHandler> typeHandler() default UnknownTypeHandler.class;
One one() default @One;
Many many() default @Many;
}
我们看到他有id、column、property、one刚刚用到过的属性,现在先讲解前三个:
id:这里的id是为了标识这个字段是否为主键,默认值为false,意为不为主键
column:这个属性是数据库的字段名
property:这个属性是实体类的属性名
那么最主要的就是后两个属性了,我们可以看到前面的写法就是将这两个属性的名字(值)对应起来:
column(数据库列名)对应property(实体类列名),这样即使数据库与实体类的名字不一样也能通过这种方式对应起来。
当我们要使这个结果集映射生效时,我们们可以通过@ResultMap注解来标识这个结果集映射的id
@ResultMap注解
这个注解很简单:
@ResultMap("studentMap")
其实如果整个类中只有一个结果集映射的话,我们可以不写这个注解的,因为他默认用唯一的注解
(3)@One注解
在上面我们看到了这个注解的用法了,他的目的就是为了解决多表查询中的一对一问题,有了这个注解你可能会忘记数据库的内连接是怎么写的了
@Result(column = "gradeId", property = "grade",
one = @One(select = "com.ssm.dao.GradeDao.findByGId",
fetchType = FetchType.LAZY)
)
)
这个注解只有两个属性,select和fetchType:
select:这个属性必须填写返回值与@Result注解中property属性值类型一致的方法全限定名。
column:这个属性的值是拿与数据库的对应的列值作为select对应方法的参数值
property:对应的实体类属性,也就是select对应方法结果映射集
fetchType:fetchType属性是加载的方式:LAZY(延迟)、EAGER(立即)、DEFAULT(默认),一对一默认是立即加载,一对多默认是延迟加载
(4)@Many注解
这是一个一对多的注解,通常我们是在另一个表中有字段引用该表字段才用一对多注解,也就是说该表作为主表时可以设一对多映射关系,那么来看看是怎么使用该注解的:
@Result(column = "studnetList", property = "gradeId",
Many = @Many(select = "com.ssm.dao.StudentDao.findByGId",
fetchType = FetchType.LAZY)
)
)
因为是一对多关系,这里column肯定是一个集合:
select:这个属性必须填写返回值与@Result注解中property属性值类型一致的方法全限定名。
fetchType:fetchType属性是加载的方式:LAZY(延迟)、EAGER(立即)、DEFAULT(默认),一对一默认是立即加载,一对多默认是延迟加载,其实与上面一对一的属性是相当的,会用一对一就会用多对多。
5.@SelectProvider、@UpdateProvider、@DeleteProvider、@InsertProvider注解
如果在程序中要使用到动态sql的话,我们就得用到@Provider系列注解解决。
其实以上三个用法相同,我只拿其中的查询做一个介绍
先看下其中一个源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SelectProvider {
Class<?> type();
String method();
}
从上面得知这个注解有两个参数,第一个type为提供动态sql的自定义类,第二个method为该类提供具体动态sql的方法。其中type中的Class类型的类必需有无参构造,method指定的方法必须是共有的,且返回值为String类型,可以为static。这个method方法返回的sql语句可以不是动态sql,也就是说他可以与@Select等价,不过要多绕一个圈子
接下来我用一个简单的栗子来讲解一下这个注解的用法:
@SelectProvider(type = "SqlProvider.class",method = "findAll")
@ResultMap("studentMap")
public Student findAll();
SqlProvider.java:
public String findAll() {
return "select * from user";
}
上面这样就是一个最简单的@SelectProvider注解的用法,但是并没有用到动态的sql,我们现在再来循序渐进看看动态是如何通过@SelectProvider来实现的:
class SelectSQL {
public String queryById(Map<String, Object> param) {
try {
return new SQL() {
{
SELECT("*");
FROM("student");
if (null != param.get("name")) {
WHERE("name = #{name}");
}
if (null != param.get("sex")) {
WHERE("sex >= #{sex}");
}
if (null != param.get("age")) {
WHERE("age <= #{age}");
}
}
}.toString();
} catch (Exception e) {
return "";
}
}
}
上面是一个相对比较完整的栗子了,从SQL()中的代码块里可以看到所有sql关键字都用大写的方法封装起来了,以后写代码只需要填字段,填表名,填条件或者其他之类的即可,像上面的WHERE(),在源码中就有AND自动拼接
在AbstractSQL<T>
类中有如下定义:
private static final String AND = ") \nAND (";
private static final String OR = ") \nOR (";
可能是没有下载源码的原因if判断中没有用本类的常量…
先不管,先不研究源码
总之SQL类继承了AbstractSQL<T>
类,从而可以得到以上所有特性,这样还是比较香的。
最后像其他动态sql如循环之类的就得在SQL中手动拼接上去这个好像并没有提供特有的处理方法,这个要大家自己认真的去专研了。
6.@CacheNamespace注解
@CacheNamespace注解主要用于mybatis二级缓存,等同于<cache>
属性。默认情况下,MyBatis 3 没有开启二级缓存,要开启二级缓存,需要在SQL 映射文件(mapper.xml)中添加一行:
<mapper namespace="cn.mybatis.mydemo.mapper.StudentMapper">
<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>
</mapper>
eviction: 缓存回收策略,有这几种回收策略
LRU - 最近最少回收,移除最长时间不被使用的对象
FIFO - 先进先出,按照缓存进入的顺序来移除它们
SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象
前提还需要在全局配置文件中开启缓存:
flushinterval 缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值
readOnly: 是否只读;true 只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 觉得数据可能会被修改
size : 缓存存放多少个元素
当然有了以上这些,最后还得在全局配置文件中加上下面这一行
<setting name="cacheEnabled" value="true"/>
现在我们再来看看注解是怎么用的,我们先来看看他的源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CacheNamespace {
Class<? extends org.apache.ibatis.cache.Cache> implementation() default PerpetualCache.class;
Class<? extends org.apache.ibatis.cache.Cache> eviction() default LruCache.class;
long flushInterval() default 0;
int size() default 1024;
boolean readWrite() default true;
boolean blocking() default false;
Property[] properties() default {};
}
可看到他只能写在类前,其中的属性都有自己的默认值。再来看看下面的使用栗子:
@CacheNamespace()
public interface StudentMapper(
@Select("select * from student where studentNo = #{studentNo}")
@Options(useCache = true)
List<Student> findById(int studentNo);
}
这样就用注解开启了二级缓存,记得不要忘记在全局配置文件中也要配置一下。
最后要说的是如果要完全抛弃映射文件可以在接口前方加上@Mapper,但是有了这个注解之后就不要在搞什么配置文件了,要么就全部用注解,要么就用配置文件。
最最后祝大家学习愉快,工资暴涨。