一对多多对一 动态SQL 缓存
1. 注解开发
1. 简单开发用注解
(本质:使用反射获得所有东西)
表现: 在接口中使用注解,相当于不需要UserMapper.xml实现类了。
在简单开发中使用,复杂开发中用xm文件方便维护。
步骤:
-
在接口中使用注解
@Select("select * from mybatis.user") List<User> getUsers();
-
在
mybatis-config.xml
文件中修改<mapper>
标签属性从resource为class,映射接口<mappers> <mapper class="com.roy.dao.UserMapper"/> </mappers>
-
测试类
@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,回滚到事务管理或者成功,查看结果,结束或者回滚。
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语句提取出来复用:
- 用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两次,观察日志中数据库只连接了一次
缓存失效的情况:
- 查询不同东西
- 增删改操作,会刷新缓存
- 手动清理(sqlSession.clearcache()
一级缓存的作用域太低,所有出现二级缓存
2. 二级缓存(namespace级别)
如果在不同的UserMapper.xml中,则不能使用,不同的namespace有不同的缓存
- 在config中的setting设置开启缓存(name,value)
- 在UserMapper.xml文件中添加
<cache/>
标签即可。
也可以添加相关的配置:
<cache
eviction="FIFO | LRU | SOFT | WEAK" 清除策略:先进先出,最近最少使用,基于gc回收,更积极的垃圾回收
flushInterval="60000" 60秒回收
size = "512" 最大缓存数目
readOnly="true" 只读
/>
二级缓存(全局缓存):
每个Mapper的标签存在相应的map里,一级缓存关闭时,自动存入二级缓存。所有的数据都会先放入一级缓存,当会话提交或者关闭的时候,才会被转存到二级缓存中,所以二级缓存需要系列化实体类
常见报错:
- 没有序列化,cache中的参数不全,或者实体类是西安Serializable接口
- 两次返回值不同: 内存地址发生变化,添加只读为true
3. 缓存原理:
一二级缓存查询顺序:
4. encache: 分布式缓存
可以用来自定义缓存策略。现在多用redis缓存