Mybatis程序示例
搭建数据库
CREATE TABLE USER( id INT(20) NOT NULL, NAME VARCHAR(30) DEFAULT NULL, pwd VARCHAR(30) DEFAULT NULL, PRIMARY KEY(id) )ENGINE=INNODB DEFAULT CHARSET=utf8 insert into `user`(`id`,`name`,`pwd`) values (1,'dwx','123456'),(2,'dyy','1223');
导入Myabtis和mysql-connector-java的jar包
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version> </dependency>
编写Mybatis核心配置文件
<?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.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis? useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="13476110270dwx"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/deng/Mapper/userMapper.xml"/> </mappers> </configuration>
编写Mybatis工具类
public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static{ try{ //创建sqlSessionFactory对象 String resource="mybatis-config.xml"; InputStream is= Resources.getResourceAsStream(resource); sqlSessionFactory=new SqlSessionFactoryBuilder().build(is); }catch(Exception e){ e.printStackTrace(); } } //获取连接 public static SqlSession getSession(){ return sqlSessionFactory.openSession(); } }
创建实体类
public class User { private int id; private String name; private String pwd; }
创建Mapper接口
public interface UserMapper { List<User> selectUser(); }
编写mapper.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.deng.Mapper.UserMapper"> <select id="selectUser" resultType="com.deng.entity.User"> select * from user </select> </mapper>
程序可能扫描不到该xml文件,配置pom.xml文件即可
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
CURD操作
select,insert,update,delete都具有如下属性:
- id:与接口中的方法名对应
- parameterType:传入Sql语句的参数类型
- resultType:sql语句返回值类型
insert,update,delete都需要session.commit()手动提交事务,否则操作不会提交到数据库
小结
- 所有的增删改操作都必须提交事务
- 接口中的参数都写上@Param
- 可以使用map来传递参数
Mybatis配置解析
- mybatis-config.xml是Mybatis的核心配置文件
- 配置内容如下
- 可以不配置元素,但是需要配置的元素的顺序必须一致,不然会报错。
environments元素
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="13476110270dwx"/> </dataSource> </environment> </environments>
- 配置Mybatis的多套运行环境,必须指定一个为默认的运行环境
- 子元素节点environment
- 具体的一套环境,id唯一标识
- 子元素节点:transactionManager事务管理器
- 子元素节点:dataSource数据源
- 使用标准的JDBC数据源接口来配置JDBC连接对象的资源
- 必须配置数据源
- 有三种内建的数据源类型
-
type="[UNPOOLED|POOLED|JNDI]")
- UNPOOLED:每次被请求时都要打开和关闭来连接
- POOLED:池,不用频繁打开和关闭连接
- JNDI:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以 集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
-
- 数据源也有很多第三方实现,如DBCP,C3P0
mappers元素
- 映射器:定义映射sql语句文件
- 告诉MyBatis去哪里找映射文件
Properties优化
把一些属性配置在priperties文件中。
创建db.properties文件
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC username=root password=13476110270dwx
将文件导入properties
<properties resource="db.properties"></properties>
typeAliases优化
给java类型设置一个别名
<typeAliases> <typeAlias type="com.deng.entity.User" alias="User"/> </typeAliases>
配置之后,在任何地方,都可以使用User来代替com.deng.entity.User
也可指定包名
<typeAliases> <package name="com.deng.entity"/> </typeAliases>
在com.deng.entity中的每一个JavaBean,会使用首字母小写的类名来作为其别名
生命周期与作用域
Mybtis执行过程
作用域分析
- SqlSessionFactoryBuilder的作用在于创建SqlSessionFactory,创建成功后就没用了,所有其作用域应该时方法作用域
- SqlSessionFactory的作用是创建一个SqlSession对象,SqlessionFactory的生命周期应该等同于MyBatis的生命周期
- SqlSessionFactory作为一个单例被应用共享,其作用域为应用作用域
- SqlSession用来执行sql,应该存活在一个业务请求中,因此其最佳作用域是请求或方法作用域
ResultMap
解决属性名和字段名不一致的问题
当sql语句查询出来的列名于实体类属性列名不一致时就无法映射,使用resultMap来指定映射关系
<mapper namespace="com.deng.Mapper.UserMapper"> <select id="selectUser" resultMap="userMap"> select * from user </select> <resultMap id="userMap" type="com.deng.entity.User"> <id column="id" property="id"></id> <!--column是查询出的数据库列名,property是对于的实体类的属性名--> <result column="name" property="name"></result> <result column="pwd" property="password"></result> </resultMap> </mapper>
分页的实现
日志工厂
MyBatis可以在控制输出日志信息
标准日志实现
<settings> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings>
Log4j
可以控制日志输出的目的地:控制台,文本等;只需要配置即可
步骤:
导入log4j的包
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
编写配置文件
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下 面的代码 log4j.rootLogger=DEBUG,console,file #控制台输出的相关设置 log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.Threshold=DEBUG log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=[%c]-%m%n #文件输出的相关设置 log4j.appender.file = org.apache.log4j.RollingFileAppender log4j.appender.file.File=./log/log.log log4j.appender.file.MaxFileSize=10mb log4j.appender.file.Threshold=DEBUG log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n #日志输出级别 log4j.logger.org.mybatis=DEBUG log4j.logger.java.sql=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.ResultSet=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG
设置日志实现
<settings> <setting name="logImpl" value="LOG4J" /> </settings>
Limit分页
在SQL层面实现分页
<mapper namespace="com.deng.Mapper.UserMapper"> <select id="selectUser" parameterType="map" resultType="com.deng.entity.User"> select * from USER limit #{startIndex},#{pageSize} </select> </mapper>
RowBound分页
在java层面实现分页
public void test1(){ SqlSession sqlSession= MybatisUtils.getSession(); int currentpage=1; int pageSize=2; RowBounds rowBounds=new RowBounds((currentpage-1)*pageSize,pageSize); List<User> users=sqlSession.selectList("com.deng.Mapper.UserMapper.selectUser",null,rowBounds); for (User user:users ) { System.out.println(user); } sqlSession.close(); }
PageHelper
使用注解开发
使用注解开发就无需mapper.xml映射文件;sql类型注解为:
- select()
- update()
- insert()
- delete()
步骤:
在接口方法上添加注解
public interface UserMapper { @Select("select * from user") List<User> selectUser(); }
在mybatis配置文件中注入
<mappers> <mapper class="com.deng.Mapper.UserMapper"/>
</mappers>
测试
public void test1(){ SqlSession sqlSession= MybatisUtils.getSession(); UserMapper userMapper=sqlSession.getMapper(UserMapper.class); List<User> userList=userMapper.selectUser(); for (User user:userList ) { System.out.println(user); } sqlSession.close(); }
注解增删改
public interface UserMapper { //查 @Select("select * from user where id=#{id}") User selectById(@Param("id")int id); //增 @Insert("insert into user (id,name,pwd) values(#{id},#{name},#{pwd})") int addUser(User user); //改 @Update("update user set name=#{name},pwd=#{pwd} where id=#{id}") int updateUser(User user); //删 @Delete("delete from user where id=#{id}") int deleteById(@Param("id")int id); }
测试
@Test public void test1() { SqlSession sqlSession = MybatisUtils.getSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user1=userMapper.selectById(1); System.out.println(user1); User user=new User(4,"zzy","hffjhsajfsdhf"); int res1=userMapper.addUser(user); int res2=userMapper.updateUser(user); int res3=userMapper.deleteById(4); System.out.println(res1); System.out.println(res2); System.out.println(res3); }
@Param注解
@Param注解用于给参数起了一个名字,在方法接收多个参数时使用。如果参数是JavaBean则不能使用
#{}与${}的区别
- #{}代表占位符
- ${}代表字符串替换
多对一处理
多个学生对应一个老师
创建数据库
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');
查询所有学生和对应的老师
查询嵌套
类似SQL中的子查询
<mapper namespace="com.deng.Mapper.StudentMapper"> <select id="getStudents" resultMap="StudentTeacher"> select * from student </select> <resultMap id="StudentTeacher" type="com.deng.entity.Student"> <association property="teacher" column="tid" javaType="com.deng.entity.Teacher" select="getTeacher"></association> </resultMap> <select id="getTeacher" resultType="com.deng.entity.Teacher"> select * from teacher </select> </mapper>
在Mybatis核心配置文件配置StudentMapper.xml即可测试
结果嵌套
类似SQL中的联表查询
<mapper namespace="com.deng.Mapper.StudentMapper"> <select id="getStudents" resultMap="StudentTeacher"> select s.id sid,s.name sname,t.name tname from student s,teacher t where s.tid=t.id </select> <resultMap id="StudentTeacher" type="com.deng.entity.Student"> <id property="id" column="sid"/> <result property="name" column="sname"></result> <association property="teacher" javaType="com.deng.entity.Teacher"> <result property="name" column="tname"></result> </association> </resultMap> </mapper>
一对多处理
一个老师拥有多个学生
查询嵌套
<mapper namespace="com.deng.Mapper.TeacherMapper"> <select id="getTeacher" parameterType="Integer" resultMap="TeacherStudent"> select * from teacher where id=#{id} </select> <resultMap id="TeacherStudent" type="com.deng.entity.Teacher"> <collection property="students" javaType="ArrayList" ofType="com.deng.entity.Student" column="id" select="getStudentsTeacherId"></collection> </resultMap> <select id="getStudentsByTeacherId" resultType="com.deng.entity.Student"> select * from student where tid=#{id} </select> </mapper>
结果嵌套
<mapper namespace="com.deng.Mapper.TeacherMapper"> <select id="getTeacher" parameterType="Integer" resultMap="TeacherStudent"> select t.id tid,t.name tname,s.name sname from teacher t,student s where t.id=s.tid and t.id=#{id} </select> <resultMap id="TeacherStudent" type="com.deng.entity.Teacher"> <id property="id" column="tid"></id> <result property="name" column="tname"></result> <collection property="students" ofType="com.deng.entity.Student"> <result property="name" column="sname"></result> </collection> </resultMap> </mapper>
小结
- association用于一对一和多对一
- collection用于一对多
- JavaType和ofType都是用来指定对象类型的
- JavaType用来指定实体类中属性的类型
- ofType用来指定List集合中元素的实体类属性类型
动态SQL
If语句
<select id="queryBlogIf" parameterType="Map" resultType="com.deng.entity.Blog"> select * from blog where <if test="title!=null"> title=#{title} </if> <if test="author!=null"> and author=#{author} </if> </select>
如果title为空的话语句就变成 select * from blog where and author=#{author},这是不对的,使用where标签解决
where
<select id="queryBlogIf" parameterType="Map" resultType="com.deng.entity.Blog"> select * from blog <where> <if test="title!=null"> title=#{title} </if> <if test="author!=null"> and author=#{author} </if> </where> </select>
如果where标签包含的标签中有返回值就会插入一个where,并且如果返回的内容是AND或者OR开头的就会将它删除
set
<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>
choose
<select id="queryBlogChoose" parameterType="Map" resultType="com.deng.entity.Blog"> select * from blog <where> <choose> <when test="title!=null"> title=#{title} </when> <when test="author!=null"> author=#{author} </when> <otherwise> and views = #{views} </otherwise> </choose> </where> </select>
choose when otherwise会按顺序选择执行,如果第一个条件满足则choose结束,否则判断第二个条件,如果都不满足就选择otherwise
SQL片段
重用SQL代码
<sql id="repeatCode"> <if test="title!=null"> title=#{title} </if> <if test="author!=null"> and author=#{author} </if> </sql> <select id="queryBlogIf" parameterType="Map" resultType="com.deng.entity.Blog"> select * from blog <where> <include refid="repeatCode"></include> </where> </select>
foreach
<select id="queryBlogForeach" parameterType="Map" resultType="com.deng.entity.Blog"> select * from blog <where> <foreach collection="ids" item="id" open="and(" close=")" separator="or"> id=#{id} </foreach> </where> </select>
- collection:指定输入对象中的集合属性
- item:每次遍历生成的对象
- open:开始遍历时的拼接字符串
- close:结束时拼接的字符串
- separator:遍历对象之间需要拼接的字符串
- select * from blog where 1=1 and (id=1 or id=2 or id=3)
缓存
MyBatis缓存默认定义两级缓存:一级缓存和二级缓存
- 默认情况,只有一级缓存开启(SqlSession级别的缓存,也称本地缓存)
- 二级缓存需要手动开启金额配置,是基于namespace级别的缓存
- Mybatis定义了Cache接口,可以通过实现Cache接口自定义二级缓存
一级缓存
- 也叫本地缓存:
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 之后如果需要获取相同的数据,直接从缓存中取,而不用查询数据库
public void test1() { SqlSession sqlSession = MybatisUtils.getSession(); UserMapper userMapper=sqlSession.getMapper(UserMapper.class); //初始查询,往数据库查询数据 User user1=userMapper.selectById(1); //再次查询会从缓存中拿数据,不会查询数据库 User user2=userMapper.selectById(1); //是同一个对象 System.out.println(user1==user2); sqlSession.close(); }
分析日志可以看出结果
一级缓存失效的4种情况
- 一级缓存是sqlSession级别的缓存,是一直开启的,我们无法关闭
一级缓存失效的4种情况:
- sqlSession不同:每个sqlSession之间的缓存是相互独立的
public void test1() { //sqlSession1 SqlSession sqlSession1 = MybatisUtils.getSession(); //sqlSession2 SqlSession sqlSession2=MybatisUtils.getSession(); //获取Mapper UserMapper userMapper1=sqlSession1.getMapper(UserMapper.class); UserMapper userMapper2=sqlSession2.getMapper(UserMapper.class); User user1=userMapper1.selectById(1); User user2=userMapper2.selectById(1); //由于sqlSession不同,此时查询出的两个对象不是同一个对象 System.out.println(user1==user2); sqlSession1.close(); }
- sqlSession相同,查询条件不同
- sqlSession相同,查询条件相同,但是两次查询之间进行了增删改操作,就会重新查询数据库
- 手动清除了一级缓存
public void test1() { //sqlSession SqlSession sqlSession = MybatisUtils.getSession(); UserMapper userMapper=sqlSession.getMapper(UserMapper.class); User user1=userMapper.selectById(1); //清除一级缓存 sqlSession.clearCache(); User user2=userMapper.selectById(1); System.out.println(user1==user2); sqlSession.close(); }
二级缓存
- 二级缓存也叫全局缓存,作用域比一级缓存高
- 基于namespace级别的缓存(对应一个Mapper(不同的sqlSession共享))
机制:
- 一个会话查询一条数据,该数据就会放在当前会话的一级缓存中
- 如果当前会话关闭,对应的一级缓存也就关闭了
- 引入二级缓存,将不同的mapper查询的数据放在对应的二级缓存中,即使关闭会话,也能从二级缓存查询内容
使用:
在MyBatis核心配置文件开启全局缓存
<setting name="cacheEnabled" value="true""></setting>
在每个mapper.xml文件里配置二级缓存
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"></cache>
测试
public void test1() { //sqlSession SqlSession sqlSession1 = MybatisUtils.getSession(); SqlSession sqlSession2 = MybatisUtils.getSession(); UserMapper userMapper1=sqlSession1.getMapper(UserMapper.class); UserMapper userMapper2=sqlSession2.getMapper(UserMapper.class); User user1=userMapper1.selectById(1); //只有会话提交或者关闭后,一级缓存中的数据才会转到二级缓存 sqlSession1.close(); User user2=userMapper2.selectById(1); System.out.println(user1==user2); sqlSession2.close(); }
结论
- 只要开启了二级缓存,对同一个Mapper查询,可以从二级缓存取数据
- 查出的数据默认是先放在一级缓存中,当会话关闭或者提交后才会将一级缓存中的数据转放在二级缓存中
缓存原理
EhCache
第三方缓存实现:java分布式缓存
使用:
导入jar包
<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"></cache>
可以编写encache.xml文件进行配置,否则直接使用默认配置