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,但是有了这个注解之后就不要在搞什么配置文件了,要么就全部用注解,要么就用配置文件。

最最后祝大家学习愉快,工资暴涨。

猜你喜欢

转载自blog.csdn.net/qq_43538697/article/details/108493470