【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[ ]]>

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

比如    表示文本内容“<”

2,使用

mybatis的sql操作有三种:

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

1)引入依赖

        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.4.1version>
        dependency>
			<dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.1.4version>
        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



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标签)


<insert id="insertUser">
     insert into t_user values(null,'admin','男')
insert> 

2>删除(delete标签)


<delete id="deleteUser">
     delete from t_user where id = 7
delete> 

3>修改(update 标签)


<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 标签)


<select id="getUserById" resultType="com.luo.bean.User"> 
     select* from t_user where id =7
select > 

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


<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 NULL isNullWrapper.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 字段, … ASC Wrapper.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参数

//注意: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.INPUT insert前自行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接数据,用List会报错,可以用Number接数据,也可以重新写一个TypeHandler(继承BaseTypeHandler>,重写getNullableResult方法:result = value == null ? null : JSON.parseObject(value, new TypeReference>(){});)
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 Type Java Type
CHAR String
VARCHAR String
LONGVARCHAR String
NUMERIC java.math.BigDecimal
DECIMAL java.math.BigDecimal
BIT boolean
BOOLEAN boolean
TINYINT byte
SMALLINT short
INTEGER int
BIGINT long
REAL float
FLOAT double
DOUBLE double
BINARY byte[]
VARBINARY byte[]
LONGVARBINARY byte[]
DATE java.sql.Date
TIME java.sql.Time
TIMESTAMP java.sql.Timestamp
CLOB Clob
BLOB Blob
ARRAY Array
DISTINCT mapping of underlying type
STRUCT Struct
REF Ref
DATALINK java.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(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中而不是List>。

@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的引用,表示开启二级缓存,相当于标签。

<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()方法。

运行拦截的对象:

对象 描述
ParameterHandler sql参数组装的过程
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这个人。
    }

你可能感兴趣的:(数据库学习,数据库)