【java学习】MyBatis使用——Java 数据持久层框架

1,概念

MyBatis是一个数据持久层(ORM)框架,封装了jdbc。把实体类和SQL语句之间建立了映射关系,是一种半自动化的ORM实现。MyBATIS需要开发人员自己来写sql语句,这可以增加了程序的灵活性,在一定程度上可以作为ORM的一种补充。
是由Apache开源项目iBatis3迁移到Github,命名为MyBatis。

1)优点

  1. 支持定制化SQL、存储过程及高级映射;
    sql写在xml中,解耦、并可重用。
  2. 避免了几乎所有的JDBC代码和手动设置参数以及获取结果集;
  3. 可以使用简单的XML或注解用于配置和原始映射,将接口和java的POJO映射成数据库种的记录;
  4. 能与各种数据库兼容;
    mybatis使用jdbc来连接数据库,只要jdbc支持的数据库mybatis也都支持。

2)缺点

  1. sql编写工作量大,复杂查询要求有一定的sql功底;
  2. sql语句依赖于具体的数据库,移植性差。

3)和其他持久化层技术对比

ORM(Object Relation Mapping)
O :object 对象
R: relation 关系
M: mapping 映射
ORM的作用就是把类转成SQL语句、可以把SQL语句转成类

  1. JDBC
    SQL夹杂在java代码中耦合度高,导致硬编码内伤;
    维护困难不易修改;
    代码冗长,开发效率低。
  2. Hibernate和JPA(全自动的ORM框架)
    操作简单,开发效率高。
    但过于复杂的sql需要绕过框架,完全由框架内部产生sql不易特殊化吹;
    反射操作太多导致数据库性能下降。
  3. MyBatis(半自动的ORM框架)
    性能出色(sql优化优势更大);开发效率逊色Hibernate和JPA。
    sql和java编码分开,功能边界清晰。==》java做业务,sql做数据。

4)字符转义:<![CDATA[ ]]>

使用<![CDATA[]]>来包含不被xml解析器解析的内容:”<”和”&”。但要注意的是:
  (1) 此部分不能再包含”]]>”;
  (2) 不允许嵌套使用;
  (3)”]]>”这部分不能包含空格或者换行。

比如<![CDATA[<]]>    表示文本内容“<”

2,使用

mybatis的sql操作有三种:

  1. 注解;
  2. xml;
  3. QueryWrapper + xm (推荐)

1)引入依赖

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
			<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

2)配置文件

配置连接、事务方式等。

3)po

@TableName(value = "tbl_user", autoResultMap = true)
public class User{
...
}

4)Mapper及常见注解

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

关于sql操作,可以使用注解,也可以直接在xml中写sql,通过xml中标签的id来绑定。
对于简单sql可以直接使用mapper的包装类,对于复杂sql写xml中更灵活,所以不推荐使用注解进行开发。
org.apache.ibatis.annotations包常见注解见后文。

5)映射文件xml


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.test.mapper.postgresql.UserMapper "> --路径,必须和类名完全一致

各种标签,对应mapper类的各种方法。注意:
1.id要和mapper接口中的方法名完全一致

</mapper>

1>添加(insert标签)

<!--int insertUser();-->
<insert id="insertUser">
     insert into t_user values(null,'admin','男')
</insert> 

2>删除(delete标签)

<!--int deleteUser();-->
<delete id="deleteUser">
     delete from t_user where id = 7
</delete> 

3>修改(update 标签)

<!--int updateUser();-->
<update id="updateUser">
     update t_user set username='abc' where id = 7
</update> 


<update>
	update user
	<set>
		<if test="username != null">username=#{username}</if>
	</set>
</update>
set元素会前置set关键字,同时也会删除无关的逗号
等价于:
<trim prefix="set" suffixoverrides=",">
...
</trim>


4>查询对象(select 标签)

<!--User getUserById();-->
<select id="getUserById" resultType="com.luo.bean.User"> 
     select* from t_user where id =7
</select > 

查询必须设置返回值:
resultType(默认映射关系)、
resultMap(自定义映射关系、一对多多对一映射关系)。
查询集合:

<!--List<User> getAllUser();-->
<select id="getAllUser" resultType="com.luo.bean.User"> 
     select* from t_user 
</select > 

5>foreach标签

批量操作,如批量增删改。sql会自动拼在缓存中,一次性提交。

批量更新或插入:

		<insert id="createPartitionTable" parameterType="java.util.ArrayList"  >

        <foreach collection="list" item="item" index="index"  separator=";">
        CREATE TABLE if not EXISTS user_${item.sDate} PARTITION OF user FOR
        VALUES
        FROM
        ( '${item.sDatetime}' ) TO ( '${item.eDatetime}' )
        </foreach>
    </insert>
    <insert id="insertBatch" parameterType="java.util.List">
        INSERT INTO t_user
        (id, name, password)
        VALUES
        <foreach collection ="userList" item="user" separator =",">
            (#{user.id}, #{user.name}, #{user.password})
        </foreach >
		</insert>

7>sql标签

<sql id="sql-1">
	...
</sql>

其他标签可以直接引用sql片段:
<...>
 <include refid="sql-1"></include>
</...>

8>if标签

  1. 判断字符串是否为空
<if test="title != null and test != '' ">
and title = #{title}
</if>
  1. 判断集合是否为空
 <if test="partAttributeList != null and partAttributeList.size > 0">
  1. boolean 类型的判断
即使是包装类型Boolean,也不需要判断null的问题
<if test="hasRelated">

9>choose-when标签

<choose>
	<when test="title != null">
	and ...
	</when>
	<when test="author != null">
	and...
	</when>
	<otherwise>
	and .....
	</otherwise>
</choose>

10>where标签

where元素只会在至少有一个子元素条件返回SQL子句的情况下,才会插入“where子句”。
若语句的开头为“and”或“or”,where元素也会将他们去除;
<where>
	<if test="...">
	and ...
	</if>
	<if test="...">
	and ...
	</if>
</where>

11>resultMap 标签(结果集映射)

<resultMap id="userInfo" type="com.luo.test.User">
	#column="数据库字段名" property="对象类的属性名"
	<result column="pid" property="password" />
	...
</resultMap>
<select id="getUserInfo" resultMap="userInfo" parameterType="String">...</select>

12>bind标签

对传入的参数处理后让下文可以使用新的变量。

<if test="userName != null and userName != ''">
	<bind name="nameLike" value="'%' + userName + '%'"/>
	and user_name like #{nameLike} 
</if>

13>函数使用

<insert id="testFun">
         DO $$
            DECLARE num INTEGER=0;
            BEGIN
             select count(*) into num as  num from (select distinct cardnum from         
             bus_data) as A;
             insert into ic_test
             select num, dt
             from bus_data limit 100;
            END  $$;
    </insert>

6)QueryWrapper

继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件
及 LambdaQueryWrapper, 可以通过 new QueryWrapper().lambda() 方法获取。

1>常见查询操作

操作方法说明举例
eq等于wrapper.eq("last_name", "皮皮虾");
ne不等于
gt大于
ge大于等于
lt小于
le小于等于
like’%值%’
notLike‘%值%’
likeLeft‘%值’
likeRight‘值%’
between在值1和值2之间betweenWrapper.between("age", 10, 20);
notBetween不在值1和值2之间
isNull字段 IS NULLisNullWrapper.isNull("email");
isNotNull字段 IS NOT NULL
in字段 IN (v0, v1, …)inWrapper.in("age", 8, 16, 26); queryWrapper.in("type",typeList)
notIn字段 NOT IN (value.get(0), value.get(1), …)
or或者orWrapper.gt(“age”, 20).or().eq(“gender”, 1);
and
orderByAsc升序:ORDER BY 字段, … ASCWrapper.orderByAsc("id");
orderByDesc降序:ORDER BY 字段, … DESC
inSql字段 IN ( sql语句 )inSqlWrapper .inSql("select id from employee where id < 10");
notInSql字段 NOT IN ( sql语句 )
exists拼接 EXISTS ( sql语句 )existsWrapper.exists(“select last_name,gender from employee where id = 1”);
notExists拼接 NOT EXISTS ( sql语句 )
			 QueryWrapper<Employee> wrapper = new QueryWrapper<>();
        //eq() 等于
        wrapper.eq("last_name", "皮皮虾");
        Employee one = employeeService.getOne(wrapper);
        System.out.println(one);

2>LambdaQueryWrapper

  public List<ServiceDictionary> findServiceDictionaryByTypeOrId(DictionaryDto dictionaryDto) {
        LambdaQueryWrapper<ServiceDictionary> lambdaQueryWrapper = Wrappers.lambdaQuery();
//设置某个字段的值       lambdaQueryWrapper.in(!CollectionUtils.isEmpty(dictionaryDto.getTypes()),ServiceDictionary::getDictionaryTypeName,dictionaryDto.getTypes());
        lambdaQueryWrapper.in(!CollectionUtils.isEmpty(dictionaryDto.getIds()),ServiceDictionary::getId,dictionaryDto.getIds());
        lambdaQueryWrapper.orderBy(true,true,ServiceDictionary::getSort);
        return serviceDictionaryMapper.selectList(lambdaQueryWrapper);
    }
LambdaQueryWrapper<Setting> wrapper = new LambdaQueryWrapper<Setting>().eq(Setting::getId, settingDto.getId())
//添加自定义sql
.apply(!Strings.isNullOrEmpty(sql), sql);
Integer count = settingMapper.selectCount(wrapper);

使用wrapper结合sql一起使用来进行查询:

  1. mapper映射
    通过@Param(Constants.WRAPPER) Wrapper wrapper传参
List<ResultDataDTO> selectData(@Param(Constants.WRAPPER) Wrapper<ResultDataDTO> wrapper);
  1. mapper.xml
    通过${ew.customSqlSegment}进行wrapper的引用
<select id="selectData" resultType="com.test.dto.ResultDataDTO">
        SELECT a.*,
        b.DeviceName
        FROM 表1 a
        INNER JOIN 表2 b
        ON a.DeviceNum = b.DeviceNum ${ew.customSqlSegment}
</select>
  1. wrapper构造并传入
	   	QueryWrapper<ResultDataDTO> wrapper = new QueryWrapper<>();
	     //如果wrapper中传入的字段在查询的多个表中存在,那么要带上前缀,即下面代码中的a.DeviceNum,这个a对应xml文件中的那个表的别名a。如果没有这样的话,会报column in where clause is amiguous
        wrapper.eq("a.DeviceNum", "30");
        //联表查或者把其它wrapper条件构造器放入xml中时使用
        xxxMapper.selectData(wrapper);

7)缓存

缓存分为一级缓存和二级缓存,默认情况下,只有一级缓存开启(SqlSession级别的缓存,也称本地缓存)
二级缓存需要手动开启和配置,是基于namespace级别的缓存
为了提高扩展性,MyBatis定义了缓存接口Cache,实现Cache可以自定义二级缓存

1>一级缓存

仅仅对一个会话中的数据进行缓存(SqlSession):
映射语句文件(mapper.xml)中所有的select语句的结果将会被缓存,insert、udpate和delete会刷新缓存
缓存会使用最近最少使用算法刷新(LRU least Recently Used)清除不需要的缓存
缓存不会定时进行刷新(也就是说,没有刷新间隔)
缓存会保存列表和对象(无论查询方法返回哪一种)的1024个引用
缓存会被视为读/写缓存,这意味着获取到的对象不是共享的,可以安全的被调用者修改,而不干扰其他调用者或线程所做的潜在修改
缓存失效情况:
1、查询不同
2、增删改操作
3、查询不使用mapper.xml
4、手动清除缓存sqlSession.clearCache();

2>二级缓存

要启用全局的二级缓存,只需要在SQL映射文件(mapper.xml)中添加即可
注意:二级缓存只作用于cache标签所在的mapper.xml文件的语句中。
如果使用JavaAPI和xml混用,在共同接口的语句将不会被默认缓存,需要使用@CacheNamespaceRef指定缓存作用区域。

<setting name="CacheEnable" value="true"/>
<cache 
	清除策略:LRU、FIFO、SOFT、WEAK
	eviction="FIFO"
	刷新间隔
	flushInterval="60000"
	引用数目
	size="512"
	只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这就提升了可观的性能
	而可读写的缓存就会通过序列化返回缓存对象的拷贝,速度上会慢一些,但是更安全,因此默认值是false
	readOnly="true"/>

8)分页

1>limit语句

<select id="..." parameterType="map">
	...
	limit #{size} offset (#{page}-1)*#{size}
</select>

注意:
如果返回值是Page<>,因为查询了total,检索效率会有影响。直接通过limit查速度就会快很多。

2>Wrapper的Page参数

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
//注意:page从1开始
Page<TTemplateData> page = new Page<>(condition.getPage(), condition.getPageSize());
IPage<TTemplateData> templateIPage = tTemplateDataMapper.selectPage(page, templateWrapper);

3>xml的Page参数

Page<TLabel> page = new Page<>(condition.getPage(), condition.getPageSize());
    IPage<TLabel> labelIPage = tLabelMapper.selectPageList(page,keys,condition.getLabelTypeName());
IPage<TLabel> selectPageList(@Param("page") Page<TLabel> page,@Param("labelName") String labelName,@Param("labelTypeName") String labelTypeName);
<select id = "selectPageList"  resultMap="BaseResultMap">
    select l.*,t.label_type_name labelTypeName from t_label l left join t_label_type t on l.label_type_id = t.id and t.status = 0
    where  l.status = 0
        <if test="labelName != null and labelName != ''">
            and l.label_name like concat('%',#{labelName},'%')
        </if>
        <if test="labelTypeName != null and labelTypeName != ''">
            and t.label_type_name = #{labelTypeName}
        </if>
</select>

3,MyBatis-plus

1)介绍

MyBatis-plus简单来说就是一款 Mybatis 增强工具,用于简化开发,提高效率。

在我们使用Mybatis时会发现,每当要写一个业务逻辑的时候都要在DAO层写一个方法,再对应一个SQL,即使是简单的条件查询、即使仅仅改变了一个条件都要在DAO层新增一个方法。MybatisPlus这个框架上手快、不用写sql语句和xml文件。直接创建查询类,就可以迅速连接数据库了。无阿点是:MybatisPlus这种过度封装的框架,强行把service层,dao层,entity层绑定在一起,耦合度太高了。

2)常用注解

见后文。

3)使用

Mybatis-Plus通过EntityWrapper(简称EW,MP封装的一个查询条件构造器)或者条件(与EW类似)来让用户自由的构建查询条件,简单便捷,没有额外的负担,能够有效提高开发效率。

public interface UserMapper extends BaseMapper<User> { }

基本CRUD:

//插入:成功会自动回写主键到实体类
result = userMapper.insert(user);
也可以直接:user.insert();

//更新:
result = userMapper.updateById(user);
也可以直接:result = user.updateById();

//删除
result = userMapper.deleteById(user.getId());
也可以直接:result = t2.deleteById();

//查询
User exampleUser = userMapper.selectById(user.getId());或者直接:User exampleUser = t1.selectById();
//批量查询:查询姓名为‘张三’的所有用户记录
List<User> userList = userMapper.selectList(
        new EntityWrapper<User>().eq("name", "张三")
); 或者直接:List<User> userList1 = user.selectList(...);
//分页查询: 10 条姓名为‘张三’的用户记录
List<User> userList = userMapper.selectPage(
        new Page<User>(1, 10),
        new EntityWrapper<User>().eq("name", "张三")
);或者直接:user.selectPage(...);
// 分页查询 10 条姓名为‘张三’、性别为男,且年龄在18至50之间的用户记录
List<User> userList = userMapper.selectPage(
        new Page<User>(1, 10),
        new EntityWrapper<User>().eq("name", "张三")
                .eq("sex", 0)
                .between("age", "18", "50")
);

4,原理

5,常用注解

1)类注解

1>@Mapper

在mapper接口使用@Mapper注解,编译之后会生成相应的接口实现类。xml文件可以不写。

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

使用 @Mapper,最终 Mybatis 会有一个拦截器,会自动的把 @Mapper 注解的接口生成动态代理类。

2>@MapperScan

每个接口可以不写@Mapper注解,统一在启动类上用@MapperScan注解。@MapperScan 配置一个或多个包路径,自动的扫描这些包路径下的类,自动的为它们生成代理类。

import org.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
//直接注入某个类
@MapperScan(basePackageClasses = com.lwh.mapper.UserMapper.class)
//直接注入某个路径
@MapperScan({"com.xttblog.mapper","com.xttblog.dao"})
@MapperScan(value = {"com.fanr.graduation.mapper"})
@ComponentScan(basePackages = {"com.xttblog.*"})

当使用了 @MapperScan 注解,将会生成 MapperFactoryBean, 如果没有标注 @MapperScan 也就是没有 MapperFactoryBean 的实例,就走 @Import 里面的配置,具体可以在 AutoConfiguredMapperScannerRegistrar 和 MybatisAutoConfiguration 类中查看源代码进行分析。

2)PO注解

1>@TableName

实体类对应表名,注解在类上。

import com.baomidou.mybatisplus.annotation.TableName;

//autoResultMap = true表示:xml 字段映射 resultMap ID
@TableName(value = "tbl_user", autoResultMap = true)
// 注解指定表名
public class User extends Model<User> {
}

2>@TableId

主键字段,注解在属性上。
通过value字段写不同的命名方式(可省略);通过type设置自增算法(默认雪花算法)。

import com.baomidou.mybatisplus.annotation.TableId;

@TableId(value="uid",type=IdType.AUTO)
private long id;
描述
IdType.AUTO数据库ID自增
IdType.NONE无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
IdType.INPUTinsert前自行set主键值
IdType.ASSIGN_ID分配ID(主键类型为Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
IdType.ASSIGN_UUID分配UUID,主键类型为String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认default方法)

除了通过type属性值来配置主键的规则外,还可以通过application.yml文件来配置:

mybatis-plus:
    global-config:
      db-config
        ##注解设置
        id-type:auto
    ##对于没有值的数据自动赋予空,设置为false可能查不出来导致返回字段不全的问题。        
    call-setters-on-nulls: true

3>@TableField

设置字段属性,通过value命名(value=可以省略),默认驼峰命名法。

//实体临时字段
@TableField(exist=false)
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import org.apache.ibatis.type.JdbcType;

		//fill为自动填充策略,
		//FieldFill: DEFAULT:默认不处理;INSERT:插⼊时填充字段;UPDATE:更新时填充字段;INSERT_UPDATE:插⼊和更新时填充字
    @TableField(jdbcType = JdbcType.TIMESTAMP, fill = FieldFill.INSERT)
    private Date timestamp;//pg中为timestamp类型
    
    @TableField(value = "category_name", jdbcType = JdbcType.VARCHAR)
    private String categoryName;
    
    @TableField(jdbcType = JdbcType.INTEGER)
    private Integer times;//pg中对应为int4
    
    @TableField(value = "user_info", typeHandler = FastjsonTypeHandler.class)
    private List<JSONObject> userList;
    
    @TableField(jdbcType = JdbcType.VARCHAR, typeHandler = JsonbTypeHandler.class)
    private Map<String, String> map = Collections.EMPTY_MAP;//pg中对应为jsonb
    
    @TableField(jdbcType = JdbcType.VARCHAR,typeHandler = JsonListTypeHandler.class)
    private List<String> list = new ArrayList<>();//pg中对应为jsonb

    @TableField(jdbcType = JdbcType.BIGINT)
    private Long time = System.currentTimeMillis();//pg中对应为int8

    @TableField(jdbcType=JdbcType.BOOLEAN)
    private Boolean state;

    @TableField(jdbcType = JdbcType.ARRAY)
    private int[] arr= new int[0];//pg中对应int4[]

    @Type(type = "DisplayAndJumpLinkJsonType")
    private DisplayAndJumpLink displayAndJumpLink;

自定义映射处理类:JsonListTypeHandler

//注意,此处用泛型,如果传值过小,会用List<Integer>接数据,用List<Long>会报错,可以用Number接数据,也可以重新写一个TypeHandler(继承BaseTypeHandler<List<Long>>,重写getNullableResult方法:result = value == null ? null : JSON.parseObject(value, new TypeReference<List<Long>>(){});)
public class JsonListTypeHandler extends BaseTypeHandler<List<?>> {

    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, List<?> objects, JdbcType jdbcType) throws SQLException {
        if (objects == null) {
            try {
                preparedStatement.setNull(i, JdbcType.OTHER.TYPE_CODE);
            } catch (SQLException e) {
                throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
                        + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
                        + "Cause: " + e, e);
            }
        } else {
            try {
                preparedStatement.setObject(i, FastJsonUtil.toJSON(objects), JdbcType.OTHER.TYPE_CODE);
            } catch (Exception e) {
                throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType
                        + " . "
                        + "Try setting a different JdbcType for this parameter or a different configuration property. "
                        + "Cause: " + e, e);
            }
        }
    }

    @Override
    public List<?> getNullableResult(ResultSet resultSet, String s) throws SQLException {
        List<?> result;
        try {
            String value = resultSet.getString(s);
            result = value == null ? null : FastJsonUtil.parse(value, List.class);
        } catch (Exception e) {
            throw new ResultMapException(
                    "Error attempting to get column '" + s + "' from result list.  Cause: " + e, e);
        }
        if (resultSet.wasNull()) {
            return new ArrayList<>();
        } else {
            return result;
        }
    }

    @Override
    public List<?> getNullableResult(ResultSet resultSet, int i) throws SQLException {
        List<?> result;
        try {
            String value = resultSet.getString(i);
            result = value == null ? null : FastJsonUtil.parse(value, List.class);
        } catch (Exception e) {
            throw new ResultMapException(
                    "Error attempting to get column #" + i + " from result list.  Cause: " + e, e);
        }
        if (resultSet.wasNull()) {
            return new ArrayList<>();
        } else {
            return result;
        }
    }

    @Override
    public List<?> getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        List<?> result;
        try {
            String value = callableStatement.getString(i);
            result = value == null ? null : FastJsonUtil.parse(value, List.class);
        } catch (Exception e) {
            throw new ResultMapException(
                    "Error attempting to get column #" + i + " from callable statement.  Cause: " + e, e);
        }
        if (callableStatement.wasNull()) {
            return new ArrayList<>();
        } else {
            return result;
        }
    }
}

描述
value字段值(驼峰命名方式,该值可无)
el是否为数据库表字段( 默认 true 存在,false 不存在 )
exist是否为数据库表字段( 默认 true 存在,false 不存在 )
strategy字段验证 ( 默认 非 null 判断,查看 com.baomidou.mybatisplus.enums.FieldStrategy )
fill字段填充标记 ( 配合自动填充使用 )
JDBC TypeJava Type
CHARString
VARCHARString
LONGVARCHARString
NUMERICjava.math.BigDecimal
DECIMALjava.math.BigDecimal
BITboolean
BOOLEANboolean
TINYINTbyte
SMALLINTshort
INTEGERint
BIGINTlong
REALfloat
FLOATdouble
DOUBLEdouble
BINARYbyte[]
VARBINARYbyte[]
LONGVARBINARYbyte[]
DATEjava.sql.Date
TIMEjava.sql.Time
TIMESTAMPjava.sql.Timestamp
CLOBClob
BLOBBlob
ARRAYArray
DISTINCTmapping of underlying type
STRUCTStruct
REFRef
DATALINKjava.net.URL[color=red][/color]

4>@TableLogic

逻辑删除,在属性上命名。

  1. 数据库中创建逻辑删除状态列,设置默认值为0; is_deleted int
  2. 实体类添加注解
@TableLogic
private Integer isDeleted;//删除时执行的是update语句

5>其它

3)Mapper注解

1>@Param

User getUserById(@Param("id")Long id);

sql中参数的两种方式:
${} 本质是字符串拼接,容易引起sql注入,不推荐使用。
#{} 本质是占位符赋值

$转换#的方式:

name like concat('%',#{keyword,jdbcType=VARCHAR},'%')
            <if test="ids != null">
                and id in
                <foreach collection="ids" item="item" open="(" separator="," close=")">
                    #{item}
                </foreach>
            </if>

2>@Select

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

public interface UserMapper extends BaseMapper<User> {
    //1. 可以直接使用 `@Select` 注解。
    //2. xml中可以使用select标签进行绑定与映射,id一定要与方法名(getUserById)保持一致。
	 @Select("select* from t_user where id =7")
    User getUserById(@Param("id")Long id);
}

3>@Options

设置缓存时间,能够为对象生成自增的key。

  @Insert("INSERT INTO city (name, state, country) VALUES(#{name}, #{state}, #{country})")
  @Options(useGeneratedKeys = true, keyProperty = "id")
  void insert(City city);

--useGenerateKey=true : 设置是否使用JDBC的getGenereatedKeys方法获取主键并赋值到keyProperty设置的领域模型属性中
--useCache = true表示本次查询结果被缓存以提高下次查询速度,
--flushCache = false表示下次查询时不刷新缓存,
--timeout = 10000表示查询结果缓存10000秒。

4>@Insert

我希望通过dao层的接口插入的数据能够返回主键的id(最终插入成功后从instance_id这个字段里面把数据放到传入对象的instanceId成员变量里面):

    @Insert("insert into instance (infos)"
                    + " ("
                    + " @{infos},"
                    + " NOW()"
                    + ")")
    @Options(useGeneratedKeys = true, keyProperty = "instanceId", keyColumn = "instance_id")
    int addInstance(Instance instance);

5>@Update

@Update("sql")
public void createIfNotExistsTable();

6>@Delete

	@Delete("delete from t_user where id=#{id}")
	int deleteUser(@Param("id") Integer id);

7>@ResultType

指定返回类型

@Select(value = "SELECT s.ID,s.CATEGORY_NAME AS name, s.PARENT_ID,p.CATEGORY_NAME AS parent_name FROM cd_category s LEFT JOIN cd_category p ON p.ID=s.PARENT_ID where FIND_IN_SET(s.id, query_children_category(${id}))")
@ResultType(ItemCategoryModel.class)
public List<ItemCategoryModel> getItemCategoryTree(@Param(value = "id") Integer id);


@Select("select role_id AS roleId from user_role where user_id = #{userId}")
@ResultType(Long.class)
List<Long> selectRoleIdListByUserId(@Param("userId") Long userId);

8>@Results和@Result

当数据库字段名与实体类对应的属性名不一致时,可以使用@Results映射来将其对应起来。column为数据库字段名,porperty为实体类属性名,jdbcType为数据库字段数据类型,id为是否为主键。

@SelectProvider(type=SqltoolMetadataSqlProvider.class, method="selectByExample")
@Results({
    @Result(column="ID", property="id", jdbcType=JdbcType.INTEGER, id=true),
    @Result(column="NAME", property="name", jdbcType=JdbcType.VARCHAR)
})
List<SqltoolMetadata> selectByExampleWithRowbounds(SqltoolMetadataCriteria example, RowBounds rowBounds);

9>@ResultMap

当这段@Results代码需要在多个方法用到时,为了提高代码复用性,我们可以为这个@Results注解设置id,然后使用@ResultMap注解来复用这段代码。

@Select({"select id, name, class_id from my_student"})
@Results(id="studentMap", value={
    @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
    @Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER)
})
List<Student> selectAll();

@Select({"select id, name, class_id from my_student where id = #{id}"})
@ResultMap(value="studentMap")
Student selectById(integer id);

10>@One

当我们需要通过查询到的一个字段值作为参数,去执行另外一个方法来查询关联的内容,而且两者是一对一关系时,可以使用@One注解来便捷的实现。

@Select({"select id, name, class_id from my_student"})
@Results(id="studentMap", value={
    @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
    @Result(column="class_id", property="myClass", javaType=MyClass.class,
        one=@One(select="com.example.demo.mapper.MyClassMapper.selectById"))
})
List<Student> selectAllAndClassMsg();

11>@Many

与@One类似,只不过如果使用@One查询到的结果是多行,会抛出TooManyResultException异常,这种时候应该使用的是@Many注解,实现一对多的查询。

@Select({"select id, name, class_id from my_student"})
@Results(id="studentMap", value={
    @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
    @Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER),
    @Result(column="id", property="gradeList", javaType=List.class,
        many=@Many(select="com.example.demo.mapper.GradeMapper.selectByStudentId"))
})
List<Student> selectAllAndGrade();

12>@SelectProvider

用自定义的provider类构造SQL语句,sql语句由type指定的类中的method方法返回。

// mapper类:
@SelectProvider(type = SalesOrderProvider.class, method = "selectSalesInformation")
    List<SalesInformation> selectSalesInformation(@Param("createDateStart") String createDateStart, @Param("createDateEnd") String createDateEnd);
//SalesOrderProvider类

public String selectSalesInformation(@Param("createDateStart") String createDateStart, @Param("createDateEnd") String createDateEnd){
        StringBuffer sql = new StringBuffer();
        sql.append(" SELECT * from user");
        return sql.toString();
    }

13>@InsertProvider

批量新增:

    @InsertProvider(type = BatchPreparationMsg.class, method = "batchAdd")
    Integer batStuAdd(@Param("list") List<PreparationMsg> list);

//BatchPreparationMsg类:
    public String batchAdd(@Param("list") List<TaskTemplate> list){
        StringBuilder sb = new StringBuilder();
        sb.append("insert into pep_preparation_msg(REF_TABLE,REF_ID,HOTEL_ID,TASK_ID,TYPE,NAME,CONTENT,USERGROUP,SHOW_FLAG,CREATE_USER,CREATE_TIME) values");
        MessageFormat mf = new MessageFormat(
                "(#'{'list[{0}].refTable},#'{'list[{0}].refId},#'{'list[{0}].hotelId},#'{'list[{0}].taskId},#'{'list[{0}].type},#'{'list[{0}].name}," +
                        "#'{'list[{0}].content},#'{'list[{0}].usergroup},#'{'list[{0}].showFlag},#'{'list[{0}].createUser},#'{'list[{0}].createTime})"
        );
        for (int i = 0; i < list.size(); i++) {
            sb.append(mf.format(new Object[]{i}));
            if (i < list.size() - 1) {
                sb.append(",");
            }
        }
        return sb.toString();
    }

14>@UpdateProvider

类似@InsertProvider,批量更新。

15>@DeleteProvider

类似@InsertProvider,批量删除。

16>@SelectKey

类似标签<selectKey>

@SelectKey(statement="SELECT LAST_INSERT_ID()", keyProperty="clusterId", before=false, resultType=Integer.class)

 - statement属性:填入将会被执行的 SQL 字符串数组。
 - keyProperty属性:填入将会被更新的参数对象的属性的值。
 - before属性:填入 truefalse 以指明 SQL 语句应被在插入语句的之前还是之后执行。
 - resultType属性:填入 keyProperty 的 Java 类型。
 - statementType属性:填入StatementPreparedStatementCallableStatement 中的 STATEMENTPREPAREDCALLABLE 中任一值填入 。默认值是 PREPARED

注意:
@SelectKey注解用在已经被 @Insert 或 @InsertProvider 或 @Update 或 @UpdateProvider 注解了的方法上。若在未被上述四个注解的方法上作 @SelectKey 注解则视为无效。

如果向数据库中插入一条数据,同时有希望返回该条记录的主键,该怎么处理了?有两种情况:
(1)数据库主键不是自增列,需要预先生成
(2)是自增列,插入之后才能获知
这两种情况都可以通过SelectKey解决,第一个种就是before,第二张是after。

17>@MapKey

mybatis返回map类型,希望多条数据放到Map<K,T>中而不是List<Map<K,T>>。

@Select("select id, name from users")
@MapKey("id")
Map<Integer, User<String>> getAMapOfUsers();

18>@ConstructorArgs

根据构造方法构造对象返回。

@Select("select id, name from product where name = #{value}")
@ConstructorArgs({
  @Arg(id = true, column="id", javaType = ProductId.class, jdbcType=JdbcType.INTEGER),
  @Arg(column="name")
})
Product getProductByNameUsingConstructor(String name);

19>@CacheNamespaceRef和@CacheNamespace

CacheNamespace的引用,表示开启二级缓存,相当于<cache>标签。

<mapper namespace="cn.mybatis.mydemo.mapper.StudentMapper">
   <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>
</mapper>

当然,前提还需要在全局配置文件中开启缓存:
<setting name="cacheEnabled" value="true"/>

或者这样使用:
@CacheNamespaceRef(name="com.luo.test.UserMapper"
public interface UserMapper extends BaseMapper<User> {
}

6,原理

1)MyBatis工作原理

2)MyBatis插件 工作原理

MyBatis 通过使用拦截器可以自定义增强MyBatis 的功能。MyBatis 通过JDK动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能。每当执行这4种对象(ParameterHandler、ResultSetHandler、Executor、StatementHandler)的方法时,就会进入拦截方法(InvocationHandler的invoke()方法。

运行拦截的对象:

对象描述
ParameterHandlersql参数组装的过程
ResultSetHandler返回结果集的组装
Executor上层的对象,sql执行全过程,包括组装参数、组装结果集返回、执行SQL过程
StatementHandler执行SQL的过程,最常用的拦截对象

自定义插件实现:

/**
 * @Intercepts 注解标记这是一个拦截器,其中可以指定多个@Signature
 * @Signature 指定该拦截器拦截的是四大对象中的哪个方法
 *      type:拦截器的四大对象的类型
 *      method:拦截器的方法,方法名
 *      args:入参的类型,可以是多个,根据方法的参数指定,以此来区分方法的重载
 */
@Intercepts(
        {
                @Signature(type = ParameterHandler.class,method ="setParameters",args = {PreparedStatement.class})
        }
)
public class ParameterInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("拦截器执行:"+invocation.getTarget());
        //目标对象
        Object target = invocation.getTarget();
        //获取目标对象中所有属性的值,因为ParameterHandler使用的是DefaultParameterHandler,因此里面的所有的属性都封装在其中
        MetaObject metaObject = SystemMetaObject.forObject(target);
        //使用xxx.xxx.xx的方式可以层层获取属性值,这里获取的是mappedStatement中的id值
        String value = (String) metaObject.getValue("mappedStatement.id");
        //如果是指定的查询方法
        if ("cn.cb.demo.dao.UserMapper.selectByUserId".equals(value)){
            //设置参数的值是admin_1,即是设置id=admin_1,因为这里只有一个参数,可以这么设置,如果有多个需要需要循环
            metaObject.setValue("parameterObject", "admin_1");
        }
        //执行目标方法
        return invocation.proceed();
    }
 
 
    @Override
    public Object plugin(Object target) {
        //如果没有特殊定制,直接使用Plugin这个工具类返回一个代理对象即可
        return Plugin.wrap(target, this);
    }
 
    @Override
    public void setProperties(Properties properties) {
    }
}

注入Mybatis:

/**
 * @Configuration:这个注解标注该类是一个配置类
 */
@Configuration
public class MybatisConfig{
 
    /**
     * @Bean : 该注解用于向容器中注入一个Bean
     * 注入Interceptor[]这个Bean
     * @return
     */
    @Bean
    public Interceptor[] interceptors(){
        //创建ParameterInterceptor这个插件
        ParameterInterceptor parameterInterceptor = new ParameterInterceptor();
        //放入数组返回
        return new Interceptor[]{parameterInterceptor};
    }
}

测试:

    @Test
    void contextLoads() {
      //传入的是1222
        UserInfo userInfo = userMapper.selectByUserId("1222");
        System.out.println(userInfo);
 			//测试代码传入的是1222,由于插件改变了入参,因此查询出来的应该是admin_1这个人。
    }
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值