admin管理员组

文章数量:1794759

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

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

1,概念

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

1)优点
  • 支持定制化SQL、存储过程及高级映射; sql写在xml中,解耦、并可重用。
  • 避免了几乎所有的JDBC代码和手动设置参数以及获取结果集;
  • 可以使用简单的XML或注解用于配置和原始映射,将接口和java的POJO映射成数据库种的记录;
  • 能与各种数据库兼容; mybatis使用jdbc来连接数据库,只要jdbc支持的数据库mybatis也都支持。
  • 2)缺点
  • sql编写工作量大,复杂查询要求有一定的sql功底;
  • sql语句依赖于具体的数据库,移植性差。
  • 3)和其他持久化层技术对比

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

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

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

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

    mybatis的sql操作有三种:

  • 注解;
  • xml;
  • 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//DTD Mapper 3.0//EN" "mybatis/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标签
  • 判断字符串是否为空
  • <if test="title != null and test != '' "> and title = #{title} </if>
  • 判断集合是否为空
  • <if test="partAttributeList != null and partAttributeList.size > 0">
  • 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一起使用来进行查询:

  • mapper映射 通过@Param(Constants.WRAPPER) Wrapper wrapper传参
  • List<ResultDataDTO> selectData(@Param(Constants.WRAPPER) Wrapper<ResultDataDTO> wrapper);
  • 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>
  • 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.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.URL[color=red][/color]
    4>@TableLogic

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

  • 数据库中创建逻辑删除状态列,设置默认值为0; is_deleted int
  • 实体类添加注解
  • @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属性:填入 true 或 false 以指明 SQL 语句应被在插入语句的之前还是之后执行。 - resultType属性:填入 keyProperty 的 Java 类型。 - statementType属性:填入Statement、 PreparedStatement 和 CallableStatement 中的 STATEMENT、 PREPARED 或 CALLABLE 中任一值填入 。默认值是 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这个人。 }

    本文标签: 持久框架数据javamybatis