MyBatis 优秀的持久层框架(一)https://developer.aliyun.com/article/1469533
7、分页
思考:为什么要分页?
- 减少数据的处理量
- 提高效率,可控范围
7.1使用limit分页
select * from user limit #{Startindex},#{pageSize}
2.Mapper.xml
<select id="getUserlist" parameterType="map" resultMap="usermap"> select * from user limit #{Startindex},#{pageSize} </select>
3.测试
@org.junit.Test public void getUserByLimit(){ SqlSession sqlSession = MybatisUtlis.getsqlsession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); HashMap<String, Integer> map = new HashMap<String, Integer>(); map.put("Startindex",0); map.put("pageSize",3); List<user> userlist = mapper.getUserlist(map); for (user user : userlist) { System.out.println(user); } sqlSession.close(); }
7.2RowBounds分页
不在使用sql实现分页
- 接口
List<user> getUserlistByRowBouds(Map<String,Integer> map);
- Mapper
<select id="getUserlistByRowBouds" resultMap="usermap"> select * from user </select>
- 测试
@org.junit.Test public void getUserlistByRowBouds(){ SqlSession sqlSession = MybatisUtlis.getsqlsession(); //RowBouds实现 RowBounds rowBounds = new RowBounds(1,2); //通过java代码层面实现分类 List<user> list = sqlSession.selectList("com.hyc.dao.UserMapper.getUserlistByRowBouds",null,rowBounds); for (user user : list) { System.out.println(user); } }
7.3、分页插件
了解即可,万一以后公司的架构师,说要使用,你需要知道他是什么东西!
8、使用注解开发
8.1、什么叫面向接口编程
什么原因 解耦
在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
1.关于接口的理解。
接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
接口的本身反映了系统设计人员对系统的抽象理解。
接口应有两类:第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
一个体有可能有多个抽象面。
抽象体与抽象面是有区别的。
2.三个面向的区别
面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法
面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现
接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题
8.2、使用注解开发
- 注解可以直接在接口上实现
@Select("select * from user") List<user> getUserList();
- 需要在核心配置文件绑定接口
<mappers> <mapper class="com.hyc.dao.UserMapper"/> </mappers>
- 测试使用
本质:反射机制实现
底层:动态代理
MyBatis详细的执行流程
方法存在多个参数,所有参数必须放在参数注解里
8.3、CRUD
自动提交
public static SqlSession getsqlsession(){ return sqlSessionFactory.openSession(true); }
编写接口,增加注解
package com.hyc.dao; import com.hyc.pojo.user; import org.apache.ibatis.annotations.*; import java.util.List; import java.util.Map; public interface UserMapper { @Select("select * from user") List<user> getUserList(); @Select("select * from user where #{id} = id") user getUserById(@Param("id") int id); @Insert("insert into user(id,name,pwd) values(#{id},#{name},#{password})") int addUser(user user); @Update("update user set name = #{name} where #{id} = id") int updateUser(user user); @Delete("delete from user where #{uid} = id") int deleteUser(@Param("uid") int id); }
测试类
【注意:我们必须要把接口注册绑定到核心配置文件】
关于@param()注解
- 基本类型的参数或者String类型需要加上
- 引用类型不需要加
- 如果只有一个基本类型,可以忽略,建议都加上
- 我们在sql中引用的就是@param()设定的属性
#{} ${}区别
#{}:安全的类似ptst
${}: 不安全的,类似st
9、lombok插件(实测好用,实体类代码偷懒神奇)
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
@Getter and @Setter @FieldNameConstants @ToString @EqualsAndHashCode @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog @Data @Builder @SuperBuilder @Singular @Delegate @Value @Accessors @Wither @With @SneakyThrows @val @var experimental @var @UtilityClass @ExtensionMethod (Experimental, activate manually in plugin settings) Lombok config system Code inspections Refactoring actions (lombok and delombok)
@Data :无参构造,get,set,toString,hashcode,equls
- 安装插件
- 导入jar包
- 之后就可以在实体类上加注解了
10、多对一处理
多对一:
- 多个学生,对应一个老师
- 对于学生这边而言,关联。。多个学生,关联一个老师【多对一】
- 对于老师而言是,集合,一个老师有很多学生【一对多】
SQL:
CREATE TABLE teacher
( id
INT(10) NOT NULL, name
VARCHAR(30) DEFAULT NULL, PRIMARY KEY (id
)) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT
INTO teacher(id
, name
) VALUES (1, '秦老师');
CREATE TABLE student
( id
INT(10) NOT NULL,name
VARCHAR(30) DEFAULT NULL, tid
INT(10) DEFAULT NULL, PRIMARY KEY (id
), KEY fktid
(tid
),
CONSTRAINT fktid
FOREIGN KEY (tid
) REFERENCES teacher
(id
)) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO student
(id
, name
, tid
) VALUES ('1', '小明', '1');
INSERT INTO student
(id
, name
, tid
) VALUES ('2', '小红', '1');
INSERT INTO student
(id
, name
, tid
) VALUES ('3', '小张', '1');
INSERT INTO student
(id
, name
, tid
) VALUES ('4', '小李', '1');
INSERT INTO student
(id
, name
, tid
) VALUES ('5', '小王', '1');
测试环境搭建
- 导入lombok
- 新建实体类,Teacher,Student
- 建立Mapper接口
- 建立Mapper.xml文件
- 在核心配置文件中绑定Mapper接口或者文件【方式很多,随意选择】
- 测试查询是否成功
按照查询嵌套处理
<?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.hyc.dao.studentMapper"> <select id="getsStuList" resultMap="StudentTeacher"> select * from student; </select> <resultMap id="StudentTeacher" type="Student"> <result column="id" property="id"></result> <result column="name" property="name"></result> <!-- 复杂的属性我们需要单独处理 association:对象 collection:集合 --> <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/> </resultMap> <select id="getTeacher" resultType="teacher"> select * from teacher where id = #{id}; </select> </mapper>
按照查询结果嵌套处理
<!--按照表关联处理--> <select id="getsStuList2" resultMap="StudentTeacher2"> select s.id sid,s.name sname,t.name tname from student s ,teacher t where s.tid = t.id </select> <resultMap id="StudentTeacher2" type="Student"> <result property="id" column="sid"></result> <result property="name" column="sname"></result> <association property="teacher" javaType="Teacher"> <result property="name" column="tname"></result> </association> </resultMap>
11.一对多处理
比如:一个老师拥有多个学生
对于老师而言,就是一对多的关系
- 环境搭建和刚才一样
- 修改实体类
@Data public class Student { private int id; private String name; private int tid; }
按照结果嵌套处理
<select id="getTeacher2" resultMap="TeacherStudent"> select s.id sid,s.name sname,t.id tid, t.name tname from student s,teacher t where s.tid = t.id and t.id=#{tid}; </select> <resultMap id="TeacherStudent" type="teacher"> <result column="tid" property="id"></result> <result column="tname" property="name"></result> <!--javatype 是一个指定属性的类型 集合中的泛型性息,我们使用oftype获取 --> <collection property="students" ofType="Student"> <result property="id" column="sid"></result> <result property="name" column="sname"></result> <result property="tid" column="tid"></result> </collection> </resultMap>
按照查询嵌套处理
<select id="getTeacher3" resultMap="TeacherStudent1"> select * from teacher where id = #{tid}; </select> <resultMap id="TeacherStudent1" type="teacher"> <collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"> </collection> </resultMap> <select id="getStudentByTeacherId" resultType="Student"> select * from student where tid = #{tid} </select>
小结
- 关联-association【多对一】
- 结合-collection【一对多】
- javaTyoe& ofType
- javaType 用了指定实体类中的类型
- ofType用来指定映射到List或者集合中的实体类型,泛型中的限制类型
注意点:
- 保证sql的可读性,尽量保证通俗易懂
- 注意一对多,多对一中,属性名和字段的问题
- 如果问题不好排查。可以使用日志,建议log4j
慢sql 1s 1000s
面试高频
- Mysql引擎
- innodb底层原理实现
- 索引
- 索引优化
12、动态sql
什么是动态sql,动态sql就是根据不同的条件产生不同的sql语句
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
12.1、搭建环境
CREATE TABLE `mybatis`.`blog` ( `id` INT(10) NOT NULL AUTO_INCREMENT COMMENT '博客id', `title` VARCHAR(30) NOT NULL COMMENT '博客标题', `author` VARCHAR(30) NOT NULL COMMENT '博客作者', `create_time` DATETIME NOT NULL COMMENT '创建时间', `views` INT(30) NOT NULL COMMENT '浏览量', PRIMARY KEY (`id`) )
创建一个基础工程
- 导包
- 编写配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 外部配置文件--> <properties resource="com/hyc/dao/db.properties"/> <!-- 可以给实体类起别名--> <settings> <setting name="logImpl" value="LOG4J"/> </settings> <typeAliases> <!-- <typeAlias type="com.hyc.pojo.user" alias="user"/>--> <package name="com.hyc.pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/hyc/dao/BlogMapper.xml"/> </mappers> </configuration>
- 编写实体类
注意编写日期要用 util包下的date
package com.hyc.pojo; import lombok.Data; import java.util.Date; @Data public class Blog { private int id; private String title; private String author; private Date createTime; private int views; }
- 编写实体类对应的Mapper接口和对应的Mapper.xml文件
if
<select id="queryBlogif" parameterType="map" resultType="blog"> select * from blog where 1=1 <if test="title != null"> and title = #{title}; </if> <if test="author != null"> and author = #{author}; </if> </select>
choose
<select id="queryBlogChoose" resultType="blog" parameterType="map"> select * from blog <where> <choose> <when test="title != null"> and title = #{title} </when> <when test="author != null"> and author = #{author} </when> <otherwise> and views = #{views} </otherwise> </choose> </where> </select>
trim(where,set)
<select id="queryBlogif" parameterType="map" resultType="blog"> select * from blog <where> <if test="title != null"> and title = #{title}; </if> <if test="author != null"> and author = #{author}; </if> </where> </select>
<update id="UpdateBlog" parameterType="map" > update blog <set> <if test="title != null"> title = #{title}, </if> <if test="author != null"> author = #{author} </if> </set> where id = #{id} </update>
所谓的动态sqL,本质上就是sql语句,只是我们可以在sql层面,去执行一个逻辑代码
sql片段
有的时候,我们可能会将一些公共的部分抽取出来,方便使用
通过include标签的refid属性来调用sql片段
<select id="queryBlogif" parameterType="map" resultType="blog"> select * from blog <where> <include refid="if-title-author"></include> </where> </select>
设置id来让sql片段可以被调用,
<sql id="if-title-author"> <if test="title != null"> and title = #{title}; </if> <if test="author != null"> and author = #{author}; </if> </sql>
注意事项:
- 最好基于单表来定义sql片段
- 最好不在sql片段存在where
Foreach
select * from User where 1=1 and (id = 1 or id=2 or id=3) <select id="selectPostIn" resultType="domain.blog.Post"> SELECT * FROM POST P WHERE ID in <foreach item="item" index="index" collection="list" open="(" separator="," close=")"> #{item} </foreach> </select>
<!-- 就是拼接sql--> <!-- select * from blog where 1=1 and (id = 1 or id=2 or id=3) --> <select id="queryBlogForeach" resultType="blog" parameterType="map"> select * from blog <where> <foreach collection="ids" item="id" open="and (" close=")" separator="or"> id=#{id} </foreach> </where> </select>
建议:
- 现在mysql写出完整的sql,再对应的修改我们的动态sql再使用,
13、缓存
如何解决查询频繁连接数据库消耗资源的问题?
一次查询的结果我们可以暂存到一个可以直接取得的地方 -->缓存
13.1、简介
我们在此查询相同市局的时候,可以直接走缓存,就不用走数据库了
- 什么是缓存 Cache
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存(内存中),用户去查询的数据不用存磁盘上(关系型数据库数据文件)查询,而是从缓存中查询,从而提高效率,也解决了高并发项目的性能问题
- 为什么使用缓存
- 减少和数据库的交互次数,减少系统的性能开销,提高系统效率
- 什么样子的数据可以使用缓存
- 经常查询并且不经常改变的数据【可以使用缓存】
13.2、Mybatis缓存
- MyBatis包含一个非常强大的查询缓存特性,它可以非常方便的定制和配置缓存,缓存可以极大的提高查询效率。
- MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高可扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存。
13.3、一级缓存
- 一级缓存也叫本地缓存:
- 与数据库同一次会话期间查询到的数据会放在本地缓存汇总
- 以后如果还需要获取相同数据,直接从缓存中取,没必要再去查询数据库
测试步骤:
- 开启日志
- 测试一个session中查询两次相同的记录
- 查看日志输出
- 可以从图看出,两次相同性质的查询使用的是同一个缓存,同一个sqlsession
缓存失效情况
1.查询两个不同的时,sql就会创建两次
2.增删改操作可能会改变原来的数据,所以必定会刷新缓存
3.查询不同的mapper
4.手动清理缓存
小结:一级缓存默认是开启的,只在一次sqlsession中有效,也就是拿到连接到关闭连接的区间段
可以把它理解成一个map。
13.4、二级缓存
- 二级缓存也叫全局缓存,一级缓存的作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存也就没了,但是我们想要的是,会话关闭了,一级缓存的数据被保存到二级缓存中
- 新的会话查询信息,就可以冲二级缓存中获取内容
- 不同的mapper查出的数据会自己放进对应的缓存(map)中
步骤:
- 开启全局缓存
<!--开启全局缓存--> <setting name="cacheEnabled" value="true"/> </settings>
我没了,但是我东西还在,我可以遗传 ----一级缓存
- 在要是用二级缓存的Mapper中开启
<cache />
也可以自定义一些参数
- 测试
- 问题,我们需要实体类序列化,否则就会报错!
小结:
- 只要开启了二级缓存,在同一个Mapper下就有效
- 所有的数据都会先放在一级缓存中
- 只有当会话提交,或者关闭的时候,才会提交到二级缓存中
13.5、缓存原理
13.6、自定义缓存
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器
要在程序中使用ehcache,先要导包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version> </dependency>
使用自定义缓存:
<!-- 在当前mapper.xml,中所使用二级缓存--> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
之后编写配置文件
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false"> <!-- diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下: user.home – 用户主目录 user.dir – 用户当前工作目录 java.io.tmpdir – 默认临时文件路径 --> <diskStore path="./tmpdir/Tmp_EhCache"/> <defaultCache eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU"/> <cache name="cloud_user" eternal="false" maxElementsInMemory="5000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" memoryStoreEvictionPolicy="LRU"/> <!-- defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。 --> <!-- name:缓存名称。 maxElementsInMemory:缓存最大数目 maxElementsOnDisk:硬盘最大缓存个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 overflowToDisk:是否保存到磁盘,当系统当机时 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。 FIFO,first in first out,这个是大家最熟的,先进先出。 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。 --> </ehcache>
Redis数据库来做缓存,非关系型数据库