[数据库 07] lombok 一对多多对一 动态 缓存

一对多多对一 动态SQL 缓存


1. 注解开发

1. 简单开发用注解

(本质:使用反射获得所有东西)

表现: 在接口中使用注解,相当于不需要UserMapper.xml实现类了。

在简单开发中使用,复杂开发中用xm文件方便维护。


步骤:

  1. 在接口中使用注解

    @Select("select * from mybatis.user")
    List<User> getUsers();
    
  2. mybatis-config.xml文件中修改<mapper>标签属性从resource为class,映射接口

    <mappers>
        <mapper class="com.roy.dao.UserMapper"/>
    </mappers>
    
  3. 测试类

    @Test
        public void test(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> users = mapper.getUsers();
            for (User user : users) {
                System.out.println(user);
            }
            sqlSession.close();
        }
    

2. Mybatis 执行流程

使用resource获取inputStream中输入的配置文件,

实例化SqlSessionFactoryBuilder构造器:

  • 使用XMLConfigBuilder对象解析配置文件流
  • 把解析的所有配置内容传递给Configuration对象

实例化SqlSessionFactory

之后流程如下:得到事务管理,创建执行器,创建SqlSession,执行sql,回滚到事务管理或者成功,查看结果,结束或者回滚。

[数据库 07] lombok 一对多多对一 动态 缓存

3. crud

如果要设置自动提交事务,在返回SqlSession的方法设置参数

SqlSessionFactory.openSession()函数可以传入参数,判断是否开启事务自动提交 永远不要使用

UserMapper.java接口中:

@Select("selsect * from user where id = #{id}")
User getUserByID(@Param("id") int id);//基本类型全部加上这个注解,引用类型不需要加(String类型需要加,其他自定义的类不加)

@Insert("insert into user(id, name, pwd) values (#{id},#{name},#{pwd})")
int addUser(User user);

@Update("updata user set name=#{name}, pwd=#{pwd}, where id = #{id}")
int updateUser(User user);

@Delete("delete from user where id=#{uid}")// 注解中取的信息名和下面注解中的一样
int deleteUser(@Param("uid") int id);//注解中的信息名优先级最高

测试中相同

@Param() 注解

在基本类型和String类型上添加,如果是引用类型不需要添加

#{} ${} : 区别: #可以放置Sql注入,相当于PreparedStatement, $相当于直接拼接,和statement一样。

2. Lombok

maven下载,记得导入插件啊!

常用注解:

@Data  // get, set, toString, hashCode, equals 包括无参构造,但添加有参构造后,需要手动添加无参构造
@AllArgsConstructor //有参构造
@NoArgsConstructor // 无参构造

3. 多对一处理

多个学生关联一个老师【多对一】

一个老师有一个学生集合 【一对多】

之前用结果集的resultType和resultMap属性描述返回值

现在用 association和 collection 处理


association: 一个复杂类型的关联 ,许多结果被包装成这种类型

collection: 一个复杂类型的集合

这两个标签中, javaType属性: 是返回值的类型,和pojo中的属性类型相同

ofType属性: 是泛型中的类型, 如list中的student


1. 多对一处理 : association

前提:

  • 数据库中:

    • Student表: id, name, tid(老师的id)

    • Teacher表: id, name

  • pojo类中:

    • Student: int id, String name, Teacher teacher;//第三个参数是一个类, 多个学生对应一个老师

    • Teacher: int id, String name

student角度去查询学生和老师信息

根据实体类查所有的student(但数据库中的字段和实体类中的不匹配,并且实体类中有个属性是复杂类型

1. 按照查询嵌套处理(子查询)(不建议)

<!-- 思路: 1. 先查询出Student,2. 根据Student的tid查询老师信息,-->
<select id="getStudent" resultMap="ST">
    select * from student
</select>

<resultMap id="ST" type="com.roy.pojo.Student">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <!-- 复杂对象需要单独处理,-->
    <association property="teacher" column="tid" javaType="com.roy.pojo.Teacher" select="getTeacher"/>
</resultMap>

<select id="getTeacher" resultType="com.roy.pojo.Teacher" parameterType="int">
    select * from teacher where id = #{id}
</select>

所以: 先查询Student, 结果集映射为ST, ST中,有复杂类型association, 实体属性和数据库字段名对应,类型设置为Teacher类,结果从getTeacher方法中查出。写getTeacher方法

2. 按照结果嵌套处理(联表查询)(建议)

    <select id="getStudent2" resultMap="ST2">
        select s.id sid, s.name sname, t.name tname from
        student s, teacher t
        where s.tid = t.id
    </select>
    
    <resultMap id="ST2" type="com.roy.pojo.Student">
        <id property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="com.roy.pojo.Teacher">
            <result property="name" column="tname"/>
        </association>
    </resultMap>

2. 一对多处理

前提:

  • 数据库中:

    • Student表: id, name, tid(老师的id)

    • Teacher表: id, name

  • pojo类中:

    • Student: int id, String name, int tid;// 修改为一个学生对应一个老师的id

    • Teacher: int id, String name, List<Student> students //一个老师有多个学生

从Teacher角度查询:

1. 通过联表查询(结果嵌套查询)

<select id="getATeacher" resultMap="TS" parameterType="int">
    select t.id tid, t.name tname, s.id sid, s.name sname
    from teacher t, student s
    where t.id = s.tid and t.id = #{id}
</select>
<resultMap id="TS" type="com.roy.pojo.Teacher">
    <id property="id" column="tid"/>
    <result property="name" column="tname"/>
    <!--        对于List对象来说,使用ofType属性来描述迭代对象中的泛型  -->
    <!--        *************和association的区别,association中使用javaType-->
    <collection property="students" column="" ofType="com.roy.pojo.Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
    </collection>
</resultMap>
输出结果为: 
Teacher(id=1, name=秦老师, students=[Student(id=1, name=小明, tid=0), Student(id=2, name=小红, tid=0), Student(id=3, name=小张, tid=0), Student(id=4, name=小李, tid=0), Student(id=5, name=小王, tid=0)])

2. 子查询(不推荐):

<select id="getATeacher" resultMap="TS2" parameterType="int">
    select * from teacher where id = #{id}
</select>
<resultMap id="TS2" type="com.roy.pojo.Teacher">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <!-- 这里给子查询传参数用coloum, javaType是list, ofType泛型类型-->
    <collection property="students" column="id" javaType="ArrayList" ofType="com.roy.pojo.Student" select="getStudent">
    </collection>
</resultMap>
<select id="getStudent" parameterType="int" resultType="com.roy.pojo.Student">
    select * from student where tid=#{id}
</select>

4. 动态SQL

根据不同的条件,生成不同的sql语句

类似于JSTL标签

  • if
  • choose(when, otherwise)
  • trim(where, set)
  • foreach

准备:

产生随机的ID序号:

import java.util.UUID;
public class RandomID {
    public static String returnRandomID(){
        return UUID.randomUUID().toString().replace("-", "");//生成UUID类,转为String类型,把中间的-替换为没有
    }
}

mybatis-config.xml文件中,<Setting标签添加 mapUnderscoreToCamelCase 为true, 可以自动让数据库中的字段名create_Column转换pojo中的CreateColumn 从下划线转换为驼峰命名法,做映射。

表blog中: 属性为: id, title, author, create_time, views


1. if

常做的是根据where子句中的一部分条件判断,例如在where后面加不加一段and +条件

where 1=1是为了当后面的条件都没有的时候,保证不出错。

当题目和作者都没有时,查询所有的用户;当有匹配的时候,只能查询出对应的一条数据

(Mybatis的BlogMapper.xml文件中的方法不能重载,只能通过传入的map参数来判断)

Mybatis接口中的方法不能重载

<select id="queryBlogIF" parameterType="map" resultType="com.roy.pojo.Blog">
        select * from blog where 1=1
        <if test="title!= null">and title = #{title}</if>
        <if test="author!= null">and author =#{author}</if>
    </select>

或者为了没有条件时不报错,可以使用where标签包住if选择标签,

where标签:在语句开头为and or等词时,可以自动去除

    <select id="queryBlogIF" parameterType="map" resultType="com.roy.pojo.Blog">
        select * from blog
        <where>
            <if test="title!= null">title = #{title}</if>
            <if test="author!= null">and author =#{author}</if>
        </where>
    </select>

2. choose(where, set)

if 的另一个模式: switch, case, otherwise

动态SQL的标签:

<select id="queryBlogIF" parameterType="map" resultType="com.roy.pojo.Blog">
    select * from blog
    <where>
        <choose>
            <when test="title != null">title = #{title}</when>
            <when test="author!= null">author = #{author}</when>
            <otherwise>1=1</otherwise>
        </choose>
    </where>
</select>

3. trim(where, set)

动态更新语句:

set标签可以自动识别逗号添加与否,所以所有的set语句中都写逗号

<update id="updateBlog" parameterType="map">
    update blog 
    <set>
        <if test="title!=null">title = #{title},   //每一个子句后面都添加逗号,set标签自动识别加不加逗号</if>
        <if test="author!=null">author=#{author},</if>
    </set>
    where id = #{id}
</update

(了解)where, set标签的父类都是trim标签,trim标签可以自定义需要添加的内容和需要检查是否略过的内容

如: where标签等价于:

<trim prefix="WHERE" prefixOverrides="AND | OR"></trim>

prefix标签表示需要插入到sql语句中的内容, prefixOverrides表示需要删除的字段

4. SQL片段(少用)

把重复使用的sql语句提取出来复用:

  1. 用SQL标签封装, 写id, 2. include标签引用SQL标签, 写refid

最好基于单表查询提取, 不要存在where和set这种标签

<sql id="title-author">
    <if test="title!=null">
        title = #{title},
    </if>
    <if test="author!=null">
        author=#{author},
    </if>
</sql>
<!-- 在使用的地方用include引用-->
<update id="updateBlog" parameterType="map">
    update blog
    <set>
        <include refid="title-author"></include>
    </set>
    where id = #{id}
</update>

5. foreach

要实现: select * from blog where id in (1,3)

函数中,传入的参数为map,方便可以传入其他的参数,所以select的parameterType为map;

map中有一个key为“mapKey”, 值为一个list的 元素,

foreach标签中,collection是map中的一个key,item是list中每一个元素的别名,用于在foreach标签中的id赋值

open是开始的拼接字符串,separator是分割的字符串,close是结束的字符串

<select id="queryBlogByForEach" parameterType="map" resultType="com.roy.pojo.Blog">
    select * from blog where id in
    <foreach collection="mapKey" item="listElement" open="(" separator="," close=")">
        #{listElement}
    </foreach>
</select>

测试

@Test
public void testForEach(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap hashMap = new HashMap();
    List<String> mapKey = new ArrayList<String>();
    mapKey.add("58bca5c98c9e48c0ab3f0c18f1e52791");
    mapKey.add("8619ef896d9b48ec836bde5fc8367da1");
    hashMap.put("mapKey", mapKey);
    List<Blog> blogs = mapper.queryBlogByForEach(hashMap);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
}

5. 缓存

把查询出来的结果放到缓存中,下次查询相同的内容走缓存而不连接数据库。解决高并发问题


读写分离,主从复制

直接写入数据库,但读的时候在服务器和数据库中间加一个缓存服务器,直接从缓存服务器读。

当数据库多时,需要主从复制。

使用缓存:经常查询但是不常改变的数据,可以减少与数据库的交互次数,减少系统开销,提高效率


1. 一级缓存(SqlSession级别)

mybatis默认开启本地会话缓存,即一个sqlSession从获取到close之间生效。

config中开启日志,

测试类中查询相同的id两次,观察日志中数据库只连接了一次

缓存失效的情况:

  1. 查询不同东西
  2. 增删改操作,会刷新缓存
  3. 手动清理(sqlSession.clearcache()

一级缓存的作用域太低,所有出现二级缓存

2. 二级缓存(namespace级别)

如果在不同的UserMapper.xml中,则不能使用,不同的namespace有不同的缓存

  1. 在config中的setting设置开启缓存(name,value)
  2. 在UserMapper.xml文件中添加<cache/>标签即可。

也可以添加相关的配置:

<cache
       eviction="FIFO | LRU | SOFT | WEAK" 清除策略:先进先出,最近最少使用,基于gc回收,更积极的垃圾回收
       flushInterval="60000" 60秒回收
       size = "512" 最大缓存数目
       readOnly="true" 只读
       />

二级缓存(全局缓存):

每个Mapper的标签存在相应的map里,一级缓存关闭时,自动存入二级缓存。所有的数据都会先放入一级缓存,当会话提交或者关闭的时候,才会被转存到二级缓存中,所以二级缓存需要系列化实体类

常见报错:

  1. 没有序列化,cache中的参数不全,或者实体类是西安Serializable接口
  2. 两次返回值不同: 内存地址发生变化,添加只读为true

3. 缓存原理:

一二级缓存查询顺序:

[数据库 07] lombok 一对多多对一 动态 缓存

4. encache: 分布式缓存

可以用来自定义缓存策略。现在多用redis缓存

上一篇:iOS WKWebView后台崩溃问题排查


下一篇:【数据结构基础C++】图论07-构造带权图