前言
-
Mybatis 是什么?
-
我们为什么要学习 Mybatis?
-
持久化是什么?
-
持久层是什么?
-
Mybatis 依赖
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <build> <!-- 更改maven编译规则 --> <resources> <resource> <!-- 资源目录 --> <directory>src/main/java</directory> <includes> <include>*.xml</include><!-- 默认(新添加自定义则失效) --> <include>**/*.xml</include><!-- 新添加 */代表1级目录 **/代表多级目录 --> </includes> <filtering>true</filtering> </resource> </resources> </build>
-
使用mybatis流程
- 导入maven依赖
- 编写mybatis.xml配置文件(配置连接数据库环境)
- 编写工具类MybatisUtils
- 通过Resource.getResourceAsStream方法获得一个流
- 通过SqlSessionFactoryBuilder().build获得sqlSessionFactory
- 通过sqlSessionFactory.open获得sqlSession
- 编写dao在属性上加注解
- 编写mapper.xml
- 在pom.xml总配置文件中设置配置文件可以在任意路径
- 在mybatis.xml配置中加入映射器映射mapper.xml文件
一、mybatis 程序
1. 思路:搭建环境 -> 导入 mybatis 的 jar 包 -> 编写代码 -> 测试
2.mybatis 程序
-
写工具类读取 mybatis 的配置文件获取 sqlSession 对象。
import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; public class MybatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { // 使用mybatis第一步,获取sqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } // 有了SqlSessionFactory,我们就可以获得sqlsession的实例了 // sqlsession对象完全包含了面向数据库执行sql命令需要的所有方法 public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(); } }
-
配置 mybatis 配置文件,连接数据库。
<!-- mybatis-config.xml --> <?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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis01?useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!--每一个mapper.xml都需要在mybatis核心配置文件中注册--> <mappers> <mapper resource="com/neu/mapper/UserMapper.xml"></mapper> </mappers> </configuration>
-
搭建环境,编写代码。(User,UserMapper 接口,UserMapper.xml)
<?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.neu.mapper.UserMapper"> <delete id="deleteUser"> delete from user where id = #{userId} </delete> <select id="selectUserList" resultType="com.neu.entity.User"> select * from user </select> <select id="selectById" resultType="com.neu.entity.User"> select * from user where id = #{id} </select> <insert id="insertUser" parameterType="com.neu.entity.User"> insert into user(name,age) values (#{name},#{age}) </insert> </mapper>
-
测试。
@Test public void test01(){ // 1.获取sqlsession对象 SqlSession sqlSession = MybatisUtil.getSqlSession(); // 2.方式一:获取UserMapper接口,去执行方法 UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.selectUserList(); for (User user : users) { System.out.println(user); } } @Test public void test05(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); //方式二:通过全限定名直接调用方法 List<User> userList = sqlSession.selectList("com.neu.mapper.UserMapper.selectUserList"); for (User user : userList) { System.out.println(user); } }
3. 可能出现的问题
- 配置文件没有注册。(mybatis 配置文件中的 没有配置)
- 绑定接口错误。(UserMapper.xml 文件中的 namespace 配置不对)
- 方法名不对。(UserMapper.xml 文件中 id 对应的方法名不对)
- 返回类型不对。(UserMapper.xml 文件的 resultType 类型不对)
- 资源导出失败问题。(需要在 pom.xml 中的 build 中配置 resources)
4. 注意点
- resource 绑定 mapper,需要使用路径 /
二、crud
1. 万能 Map
-
Map 传递参数,直接在 sq 中取出 Map 的 key。
Map map = new Map(); map.set("name","zhangsan"); map.set("userId",1); mapper.getUserById(map); // UserMapperUser getUserById(Map map); // UserMapper.xml // <select id="getUserById" parameterType="map"> // select * // from user // where username=#{name} or user_id=#{userId} // </select>
-
对象传递参数,直接在 sq 中取对象的属性。
User user = new User(); user.setUsername("zhangsan"); user.setUserId(1); mapper.getUserById(user); // UserMapperUser getUserById(User user); // UserMapper.xml // <select id="getUserById" parameterType="user"> // select * // from user // where username=#{username} or user_id=#{userId} // </select>
-
只有一个基本类型参数的情况下,可以直接在 sq 中取到,省略 parameterType。
mapper.getUserById(id); // UserMapperUser getUserById(int id); // UserMapper.xml // <select id="getUserById"> // select * // from user // where username=#{username} or user_id=#{userId} // </select>
-
多个参数传递时,用 Map,或者 @RequestParm注解。
2. 模糊查询
-
java 代码执行时传递通配符。
List<User> userList = mapper.getUserList("%李%");
-
在 sql 拼接时使用通配符。
select * from user where username like "%"#{username}"%"
三、配置优化
1. 总配置
#db.properties 加上jdbc前缀避免出错(连接不到数据库)jdbc.driver=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/mybatis?useEncoding=true&characterEncoding=utf-8jdbc.username=rootjdbc.password=xxx
<!-- mybatis-config.xml --><?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="db.properties"> <!--db.properties在resources目录下可以直接写文件名--> <property name="username" value="root"/> <property name="password" value="root"/> </properties> <!--settings配置--> <settings> <!--全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认true--> <setting name="cacheEnabled" value="true"/> <!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。也就是用到时才加载 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。默认false--> <setting name="lazyLoadingEnabled" value="默认false"/> <!--允许JDBC支持自动生成主键,需要数据库驱动支持。默认false--> <setting name="useGeneratedKeys" value="false"/> <!--指定MyBatis所用日志的具体实现,未指定时将自动查找。默认未指定--> <setting name="logImpl" value="LOG4J"/> </settings> <!--别名设置:1给实体类起别名,2配置一个包名--> <typeAliases> <!--<typeAlias type="com.neu.entity.User" alias="user"></typeAlias>--> <package name="com.neu.entity"/> </typeAliases> <environments default="development"> <!--环境一--> <environment id="development"> <!--mybatis事务管理器:默认时JDBC,还有一个managed--> <transactionManager type="JDBC"/> <!--mybatis数据源:默认POOLED,还有UNPOOLED和JNDI--> <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> <!--环境二--> <environment id="test"> <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映射器--> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>
2. 相关配置属性
-
properties(属性)
-
settings(设置)
-
typeAliases(类型别名)
-
environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
- environment(环境变量)
-
mappers(映射器)
-
方式一:resource(推荐使用)
<mappers> <!--每一个Mapper.xml文件都需要在核心配置文件中注册--> <mapper resource="com/neu/mapper/UserMapper.xml"/></mappers>
-
方式二:使用 class 文件绑定注册
<mappers> <!--每一个接口都需要在核心配置文件中注册--> <mapper class="com.neu.mapper.UserMapper"/></mappers><!-- 注意:--><!--1接口和它的Mapper.xml配置文件必须同名。--><!--2接口和它的Mapper.xml配置文件必须在同一个包下。-->
-
方式三:使用扫描包进行绑定注册。
<mappers> <package name="com.neu.mapper"/></mappers><!--注意:--><!--1接口和它的Mapper.xml配置文件必须同名。--><!--2接口和它的Mapper.xml配置文件必须在同一个包下。-->
-
四、生命周期和作用域
生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题
- SqlSessionFactoryBuider 作用域。
- 一旦创建了 SqlSessionFactory,就不再需要它了
- 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(局部方法变量)
- SqlSessionFactory 作用域。
- SqlSessionFactory 可以想象成连接池。
- SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- 因此 SqlSessionFactory 的最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
- SqlSession 作用域。
- 可以视为连接到连接池的一个请求。
- SqlSession 的实例不是线程安全的,因此是不能被共享的。
- 所以它的最佳的作用域是请求或方法作用域。
- 用完之后关闭,否则资源会被占用。
- 为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。
五、ResultMap
1.ResultMap 结果集映射。
-
起别名
<mapper namespace="com.neu.mapper.UserMapper"> <select id="getUserById" resultType="com.neu.entity.User"> select id,username,pwd as password from User where id = #{id} </select></mapper>
-
ResultMap 结果集映射(可以只映射字段名和属性名不同的)
<resultMap id="userMap" type="com.neu.entity.User"> <id column="id" property="id"/> <!--数据库字段id和实体类属性id名称一致可省略id的映射--> <result column="username" property="username"/> <!--数据库字段username和实体类属性username名称一致可省略username的映射--> <result column="pwd" property="password"/> <!--数据库字段pwd和实体类属性password名称不一致不可省略的映射--></resultMap><select id="getUserById" resultMap="userMap"> select id,username,pwd from User where id = #{id}</select>
2.ResultMap 多对一映射
2.1 按照查询嵌套处理
@Datapublic class Student { private int id; private String username; private Teacher teacher;}
<mapper namespace="com.neu.mapper.StudentMapper"> <!-- 思路:1.查询所有的学生 2.根据查询出来的学生表的tid字段,查找对应的老师 --> <resultMap id="studentTeacherMap" type="com.neu.entity.Student"> <id property="id" column="id"></id> <result property="username" column="username"/> <!--对象:association,tid是学生表中关联的老师的id--> <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/> </resultMap> <select id="getStudentList" resultMap="studentTeacherMap"> select * from student </select> <select id="getTeacher" resultType="Teacher"> select * from teacher where id = #{tid} <!--这个参数名推荐和上面column="tid"保持一致,但是不保持一致也不会出错--> </select></mapper>
2.1 按照结果嵌套处理
<mapper namespace="com.neu.mapper.StudentMapper"> <resultMap id="studentTeacherMap" type="com.neu.entity.Student"> <id property="id" column="id"></id> <result property="username" column="username"/> <!--对象:association--> <association property="teacher" javaType="Teacher"> <id property="id" column="tid"></id> <result property="username" column="tname"></result> </association> </resultMap> <select id="getStudentList" resultMap="studentTeacherMap"> select s.id,s.username,t.id tid,t.username tname from student s,teacher t where s.tid=t.id </select></mapper>
3.ResultMap 一对多映射
3.1 按照查询嵌套处理
@Datapublic class Teacher { private int id; private String username; private List<Student> studentList;}
<mapper namespace="com.neu.mapper.TeacherMapper"> <!-- 思路:1.查询指定的老师 2.根据查询出来的老师的id字段别名是ttid,查找老师下对应的所有学生 --> <resultMap id="teacherStudentMap" type="Teacher"> <id property="id" column="ttid"></id> <!--ttid是老师表的id--> <collection property="studentList" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="ttid"/> </resultMap> <!--按照查询嵌套处理--> <select id="getTeacherById" resultMap="teacherStudentMap"> select t.id ttid,t.* from teacher t where id=#{tid} </select> <select id="getStudentByTeacherId" resultType="Student"> select * from student where tid = #{ttid} </select> </mapper>
3.2 按照结果嵌套处理
<mapper namespace="com.neu.mapper.TeacherMapper"> <resultMap id="teacherStudentMap" type="Teacher"> <id property="id" column="tid"/> <result property="username" column="tname"/> <!-- 复杂的属性单独处理,集合:collection javaType=""指定实体类属性的类型; 集合中的泛型信息,使用ofType获取。 --> <collection property="studentList" ofType="Student"> <id property="id" column="tid"/> <result property="username" column="sname"/> <result property="tid" column="tid"/> </collection> </resultMap> <!--按结果嵌套查询--> <select id="getTeacherById" resultMap="teacherStudentMap"> select s.id sid,s.username sname,t.id tid,t.username tname from student s,teacher t where s.tid=t.id and t.id=#{tid} </select> </mapper>
六、日志工厂
1.mybatis 的日志配置
使用哪个日志实现在 mybatis-config.xml 设置中配置
- SLF4J
- LOG4J(掌握)
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING(掌握:标准日志输出)
- NO_LOGGING0
2.log4j
- Log4j 是 Apache 的一个开源项目,通过使用 Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI 组件。
- 我们也可以控制每一条日志的输出格式。
- 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
- 最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
3.mybatis 使用 log4j 步骤
-
导入 log4j 的 jar 包。
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version></dependency>
-
设置 mybatis 配置文件的日志实现类是 LOG4J
<settings> <!--指定MyBatis所用日志的具体实现,未指定时将自动查找。默认未指定--> <setting name="logImpl" value="LOG4J"/></settings>
-
书写 log4j.properties 配置文件。
# 将等级为DEBUG的日志信息输出到console和file两个目的地log4j.rootLogger=DEBUG,console,file# 控制台输出相关配置log4j.appender.console=org.apache.log4j.ConsoleAppenderlog4j.appender.Threshold=DEBUGlog4j.appender.console.Target=System.outlog4j.appender.console.layout=org.apache.log4j.PatternLayoutlog4j.appender.console.layout.ConversionPattern=[%-5p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%c] \:%m%n# 文件输出的相关配置log4j.appender.file=org.apache.log4j.RollingFileAppenderlog4j.appender.file.File=./logs/all.loglog4j.appender.file.MaxFileSize=10mblog4j.appender.file.Threshold=DEBUGlog4j.appender.file.layout=org.apache.log4j.PatternLayoutlog4j.appender.file.layout.ConversionPattern=[%-5p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%c] \:%m%n# 日志输出级别log4j.logger.org.mybatis=DEBUGlog4j.logger.java.sql=DEBUGlog4j.logger.java.sql.Statement=DEBUGlog4j.logger.java.sql.ResultSet=DEBUGlog4j.logger.java.sql.PreparedStatement=DEBUG
七、分页和注解开发
1.limit 分页
// i : 为查询结果的索引值(默认从0开始)// n : 为查询结果返回的数量select * from user limit i,n;// 传一个参数时,意思是从第一个数据开始查询n个数据。select * from user limit n;
2.RowBounds 分页
// 代码成设置分页@Testpublic void test02(){ SqlSession sqlSession = SqlSessionUtil.getSqlSession(); RowBounds rowBounds = new RowBounds(1, 2); List<User> userList = sqlSession.selectList("com.neu.mapper.UserMapper.selectList", rowBounds); }
3.mybatis 的 PageHelper 分页插件。
4. 注解开发
@Select("select * from user")List<User> selectUser();@Insert("")void insertUser(User user);@Update("")void updateUser(User user);@Delete("")void deleteUserById(int id);
八、mybatis 执行流程
九、动态 SQL
- 动态 SQL 是指根据不同的条件生成不同的 SQL 语句。
- 动态 SQL 本质还是 SQL 语句,只是我们可以在 SQL 层面,执行一个逻辑代码。
- 动态 SQL 就是在拼接 SQL 语句,我们只要保证 SQL 正确性,按照 SQL 格式去排列组合就可以了。
1.if
<select id="queryBlogIF" paremterType="map" resultType="blog"> select * from blog where 1=1 <if test="title != null"> title = #{title} </if> <if test="author != null"> and author = #{author} </if> </select>
2.choose,when,otherwise
<select id="queryBlogChoose" paremterType="map" resultType="blog"> select * from blog <where> <choose> <!--只会选择其中一个去执行--> <when test="title != null"> title = #{title} </when> <when test="author != null"> and author = #{author} </when> <otherwise> and views = #{views} </otherwise> </choose> </where> </select>
3.trim(where,set)
标签作用:
- where 标签元素中有子元素返回任何内容的情况下才插入 “where” 子句。
- 如果子句的开头为”AND” 或”OR”,where 标签元素会将他们去除。
标签作用:
-
set 标签元素会动态的在行首插入 set 关键字,并会删除额外的逗号。(这些逗号是在使用条件语句给列赋值时引入的)
<update id="updateAuthorIfNecessary"> update Author <set> <if test="username != null"> username=#{username}, </if> <if test="password != null"> password=#{password}, </if> <if test="email != null"> email=#{email}, </if> <if test="bio != null"> bio=#{bio} </if> </set> where id=#{id} </update>
4.foreach
<select id="selectPostIn" resultType="User"> select * from user u where id in <foreach item="id" index="index" collection="ids" open="(" separator="," close=")"> #{id} </foreach> </select>
5.SQL 片段
-
可以将一些功能的部分抽取出来,方便复用。
5.1 使用 SQL 标签抽取公共的部分。
<sql id="if-title-author"> <if test="title != null"> title = #{title} </if> <if test="author != null"> and author = #{author} </if> </sql>
5.2 在需要的地方使用 include 标签引用即可。
<select id="queryBlogIF" paremterType="map" resultType="blog"> select * from blog <where> <include refid="if-title-anthor"></include> </where> </select>
十、缓存
- 什么是缓存 [Cache]?
- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中査询,从而提高査询效率,解决了高并发系统的性能问题。
- 为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率。
- 什么样的数据能使用缓存?
- 经常査询并且不经常改变的数据。
1.mybatis 缓存
- mybatis 默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启。(SqlSession 级别的缓存)
- 二级缓存需要手动开启和配置,基于 namespace 级别的缓存。
- 为了提高扩展性,mybatis 定义了缓存接口 Cache。(我们可以通过实现 Cache 接口来自定义二级缓存)
2. 一级缓存
2.1 测试步骤
- 开启日志。
- 测试在一个 SqlSession 中查询两次相同的记录。
- 查看日志输出。(只查询了一次数据库 SQL,第二次直接从缓存中数据)
2.2 一级缓存失效的情况
- 查询不同的东西。
- 增删改操作,可能会改变原来的数据,所以必定会刷新缓存。
- 查询不同的 mapper.xml
- 手动清理缓存。(sqlSession.clearCache ();)
2.3 一级缓存总结
- 一级缓存默认是开启的。
- 一级缓存只在一次 SqlSession 中有效,即拿到连接到连接关闭的这个区间。
3. 二级缓存
3.1 二级缓存使用步骤
-
开启全局缓存。
<settings> <!--默认是true,建议显式的在配置文件中声明--> <setting name="cacheEnabled" value="true"/></settings>
-
在要使用二级缓存的 mapper.xml 中开启。
<!--直接开启使用--><cache/><!--也可以自定义参数开启使用--><cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
LRU – 最近最少使用:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
-
测试。
- 需要把实体类序列化(实体类实现 Serializable 接口),否则可能会报错。
3.2 二级缓存总结
-
只要开启了二级缓存,在同一个 mapper 下有效。
-
所有的数据都会先放在一级缓存中。
-
只有当会话提交或关闭的时候,才会提交到二级缓存中。
-
可以在 mapper.xml 中的查询语句定义是否使用缓存
<select id="getUser"resultType="user" userCache="false"> select * from user</select>
4. 缓存原理
5. 自定义缓存 - ehcache
Ehcache 是一种广泛使用的开源 Java 分布式缓存。主要面向通用缓存
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
配置一个 ehcache.xml 配置
总结
- mybatis 是一款优秀的持久层框架,可以把数据持久化;它支持定制化 sql,存储过程,结果集映射等。
- 使用的人多,传统的 jdbc 代码太复杂了,mybatis 框架简化了流程,自动化。
- 持久化是数据从瞬时状态转化到持久状态。内存的特性是断电即失,持久化指存到数据库中,io 文件持久化等。
- 持久层是完成持久化工作的代码块。
面试题
- MySQL 引擎
- InnoDB 底层原理
- 索引
- 索引优化
- 读写分离
- 主从复制