1. 简介
1.1 Mybatis介绍
- MyBatis是一款优秀的持久层框架
- 它支持定制化SQL、存储过程以及高级映射。
- MyBatis避免了几乎所有JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML或注解来配置和映射原生类型、接口和Java的POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
1.2 安装Mybatis
-
Maven仓库
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency>
1.3 持久层
- 进行持久化的代码块
- 层界限十分明显
1.4 为什么需要Mybatis
优点:
- 简单易学
- 灵活
- sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql
2. 第一个Mybatis程序
环境搭建
-
数据库
CREATE DATABASE `mybatis`; USE `mybatis`; CREATE TABLE `user`( `id` INT(20) NOT NULL PRIMARY KEY, `name` VARCHAR(30) DEFAULT NULL, `pwd` VARCHAR(30) DEFAULT NULL )ENGINE=INNODB DEFAULT CHARSET=utf8; INSERT INTO `user` (`id`,`name`,`pwd`) VALUES (1,'狂神','123456'), (2,'张三','123456'), (3,'李四','123456')
-
新建Maven项目:Mybatis-Study,删除src作为父工程,导入依赖
<dependencies> <!--Mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <!--Mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.18</version> </dependency> </dependencies>
第一个Mybatis程序
创建一个module,mybatis-01:
-
编写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.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="admin"/> </dataSource> </environment> </environments> </configuration>
-
编写mybatis工具类,获取sqlsessionFactory和sqlSession
//sqlSessionFactory -----> sqlSession public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { // 第一步:获取sqlSessionFactory对象! String resource = "mybbatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } // 创建sqlSession,SqLSession完全包含了面向数据库执行SQL命令所需的所有方法。 public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
-
编写代码:
-
实体类
@Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String pwd; }
-
Dao及其实现类,接口实现类由原来JDBC的impl转换为一个Mapper配置文件!
public interface UserDao { List<User> getUserList(); }
<!--命名空间绑定一个对应的Dao接口--> <mapper namespace="com.kuang.dao.UserDao"> <!--resultType:返回类型,如果接口返回的是集合,只要写集合泛型中的东西的全限定名!!!--> <select id="selectBlog" resultType="com.kuang.pojo.User"> select * from mybatis.user </select> </mapper>
-
-
Junit测试:
public class UserDaoTest { @Test public void test(){ // 1.获取SqlSession SqlSession sqlSession = MybatisUtils.getSqlSession(); // 方式一:getMapper UserDao userDao = sqlSession.getMapper(UserDao.class); List<User> userList = userDao.getUserList(); //方式二: List<User> userList = sqlSession.selectList("com.kuang.dao.UserDao.getUserList"); for (User user : userList) { System.out.println(user); } // 3.关闭sqlSession sqlSession.close(); } }
-
报错:没有配置mapper文件
<!--核心配置文件--> <configuration> <!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册--> <mappers> <mapper resource="com/kuang/dao/UserMapper.xml"/> </mappers> </configuration>
-
还是报错:可能存在错误,是因为配置文件没有生效,我们在子pom和父pom中都加入如下:
<!--在bui7d中配置resources,来防止我们资源导出失败的问题--> <build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
-
报错:
解决方案:把mybatis-config.xml文件上文件头的encoding="UTF-8"改为encoding="UTF8"
-
报错:
解决方案:数据库连接的时候加上时区:
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"/>
成功:
-
3. 增删改查实现
查询语句
- 标签为
- id:就是对应的namespace中绑定接口的方法名
- resultType:Sql语句执行的返回值!如果返回的是集合,一定要写集合里的泛型!
- parameterType:Sql语句需要传递的参数类型
增加语句
标签为
//插入用户int addUser(User user);
在Mapper.xml中,如果没有传参,他还是可以拿到实体类的属性,直接通过#{}即可。
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into mybatis.user(id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
增删改一定要提交事务!!!!!!!!!
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
mapper.addUser(new User(4,"ran","123456"));
//提交事务
sqlSession.commit();
sqlSession.close();
}
修改语句
标签为
//修改用户
int updateUser(User user);
<update id="updateUser" parameterType="com.kuang.pojo.User">
update mybatis.user set name = #{name},pwd = #{pwd} where id = #{id}
</update>
@Test
public void test3(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
mapper.updateUser(new User(4,"ranhaifeng","123456"));
//提交事务
sqlSession.commit();
sqlSession.close();
}
删除语句
标签为
//删除用户
int deleteUser(int id);
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id}
</delete>
@Test
public void test4(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
mapper.deleteUser(4);
//提交事务
sqlSession.commit();
sqlSession.close();
}
4. ParameterType:Map
背景
在插入和修改的时候,如果传入一个User,而User的字段全限定名非常长,那么就会十分冗余。
实现
-
Map具有以下优点:
- 不用写插入数据类型的全限定名
- 可以自定义插入的数值名,即自己的key,SQL语句中是自己的key,然后我们向map中的对应的key设值,值就会被传入sql语句中!
- 方法有多个参数的时候,只能用map
-
Map实现插入:
int insertUser(Map<String,Object> map);
<insert id="insertUser" parameterType="map"> insert into mybatis.user(id,name,pwd) values (#{helloid},#{helloname},#{hellopassword}) </insert>
public class UserDaoTest { @Test public void test(){ // 1.获取SqlSession SqlSession sqlSession = MybatisUtils.getSqlSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); Map<String, Object> map = new HashMap<String, Object>(); map.put("helloid",4); map.put("helloname","ranhaifeng"); map.put("hellopassword","123456"); userDao.insertUser(map); System.out.println(map); sqlSession.commit(); sqlSession.close(); } }
5. 模糊查询
public interface UserDao { List<User> selectUser(String val);}
方式1:直接在传入参数时带上通配符
List<User> users = userDao.selectUser("%张%");
<select id="selectUser" parameterType="String" resultType="com.kuang.pojo.User">
select * from mybatis.user where name like #{val}
</select>
会有sql注入的危险!
方式2:直接在sql语句中写死(推荐)
List<User> users = userDao.selectUser("张");
<select id="selectUser" parameterType="String" resultType="com.kuang.pojo.User">
select * from mybatis.user where name like "%"#{val}"%"
</select>
6. 配置解析
核心配置文件
- 核心配置文件是Mybatis-config.xml
6.1 环境配置
- 事务管理器有JDBC(默认)和MANAGED两种,后者一般没啥用
- 数据源的三种:
- UNPOOLED:每次被请求时打开和关闭连接,很慢
- POOLED(默认)
- JNDI
6.2 属性优化
我们可以通过properties属性来实现引用配置文件,上述的property中的value值都是可以动态替换的,即可以在典型的Java属性文件中配置,也可通过Properties元素的子元素来传递。
-
编写一个配置文件db.properties
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC username=admin password=root
-
在Mybatis核心配置文件中引入:
-
可以直接引入外部文件
-
可以在其中增加一些properties
driver=com.mysql.cj.jdbc.Driverurl=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCusername=rootpassword=admin
<!--核心配置文件--> <configuration> <properties resource="db.properties"/> </configuration>
-
当外部配置文件和核心配置文件内的properties冲突时,优先外部配置文件
-
6.3 别名优化
问题引入
- 类型别名是为Java类型设置一个短的名字
- 存在的意义是用来减少类完全限定名的冗余
实现
-
普通起别名方法
<!-- 给实体类起别名 --> <typeAliases> <typeAlias type="com.kuang.pojo.User" alias="User"/> </typeAliases>
<mapper namespace="com.kuang.dao.UserDao"> <select id="selectUser" parameterType="String" resultType="User"> select * from mybatis.user where name like "%"#{val}"%" </select> </mapper>
-
也可以指定一个包名,Mybatis会在扫描这个包的Java Bean,在没有注解的情况下,默认别名就是这个类首字母小写的类名(其实大写也跑的出来!)
<!-- 给实体类起别名 --> <typeAliases> <package name="com.kuang.pojo"/> </typeAliases>
-
直接在实体类上通过注解**@Alias**也可以指定别名(一定要做第二步,不然会失效!)
@Alias("hello") public class User { private int id; private String name; private String pwd; }
6.4 设置settings
介绍
这是Mybatis中及其重要的调整设置!
6.5 映射器
-
方式一:
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册--> <mappers> <mapper resource="com/kuang/dao/UserMapper.xml"/> </mappers>
-
方式二:使用class文件绑定注册:
<mappers> <mapper class="com.kuang.dao.UserDao"/> </mappers>
注意点:
- 接口和它的Mapper配置文件必须同名且在同一个包下
-
方式三:使用扫描包进行绑定:(接口和它的Mapper配置文件必须同名且在同一个包下)
<mappers> <!-- <mapper resource="com/kuang/dao/UserMapper.xml"/> --> <!-- <mapper class="com.kuang.dao.UserDao"/>--> <package name="com.kuang.dao"/> </mappers>
7. 生命周期和作用域
SqlSessionFactoryBuilder
一旦创建就不需要了,局部变量
SqlSessionFactory
- 可以想象为数据库连接池
- 一旦创建,没有任何理由丢弃它或重新创建一个实例
- 最佳作用域是应用作用域
- 最简单的使用就是使用单例模式或者静态单例模式
SqlSession
- 连接到连接池的一个请求!
- 需要开启跟关闭
- 不是线程安全,用完之后需要关闭,否则资源被占用
8. ResultMap结果集映射
属性名和字段名不一致问题
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String password;
}
<mapper namespace="com.kuang.dao.UserDao">
<select id="getUserById" parameterType="int" resultType="com.kuang.pojo.User">
select * from mybatis.user where id = #{id}
</select>
</mapper>
测试出现问题:
解决方式1:起别名!
select id,name,pwd as password from mybatis.user where id = #{id}
解决方式2:resultMap结果集映射
结果集映射:
id name pwdid name pwssword
-
ResultMap的设计思想是,对于简单的语句根本不需要结果映射,复杂的语句只需要描述他们的关系
-
ResultMap最优秀的地方在于,不需要显式指定字段名一致的属性:(只写字段名不同的属性)
9. 日志
9.1 日志工厂
介绍
如果一个数据库操作出现了问题,我们需要拍错,日志就是最好的助手。在Mybatis中具体使用哪一个日志实现,在setting中指定!
- LOG4J
- STDOUT_LOGGING(标准日志输出)
标准日志工厂实现
<!--核心配置文件-->
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>
9.2 Log4j
介绍
- 通过Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
- 可以控制每一条日志的输出格式
- 通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程
- 通过一个配置文件来灵活配置,而不需要修改应用代码。
使用
-
先导入包
<dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies>
-
log4j.properties
#将等级为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/kuang.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
-
配置log4j为日志的实现:
<!--核心配置文件--> <configuration> <settings> <setting name="logImpl" value="LOG4J"/> </settings> </configuration>
-
使用!直接测试运行刚才的查询
-
简单使用:
-
在要使用log4j的类中,导入包import org.apache.log4j.Logger
-
日志对象,参数为当前类的class对象
static Logger logger = Logger.getLogger(UserDaoTestTwo.class);
-
打不开.log文件?
-
测试
public class UserDaoTestTwo { static Logger logger = Logger.getLogger(UserDaoTestTwo.class); @Test public void getUserList(){ logger.info("info:进入了testLog4j"); logger.debug("info:进入了testLog4j"); logger.error("info:进入了testLog4j"); } }
-
10. 分页
为什么分页?
减少数据的处理量
Limit分页
select * from user limit 0,2; //从下标第0个开始,每页显示2个人
select * from user limit 3; //从下标第0个开始,每页显示3个人,相当于[0,3]
10.1 Limit分页
Mybatis分页
思路:分页需要传2个limit参数过去,多个参数优先考虑map,通过往map中放入2个键值对,然后在SQL语句中取map的key,从而拿到2个参数。
-
接口
List<User> getUserByLimit(Map<String,Object> map);
-
Mapper.xml
<select id="getUserByRowBounds" resultType="user"> select * from mybatis.user </select>
-
测试
@Test public void getUserList(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //RowBounds实现 RowBounds rowBounds = new RowBounds(1, 2); List<User> userList = sqlSession.selectList("com.kuang.dao.UserDao.getUserByRowBounds", null, rowBounds); for (User user : userList) { System.out.println(user); } sqlSession.close(); }
10.2 RowBounds分页(了解)
-
接口
List<User> getUserByRowBounds(Map<String,Object> map);
-
mapper
<select id="getUserByRowBounds" resultType="user"> select * from mybatis.user </select>
-
测试
@Test public void getUserList(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //RowBounds实现 RowBounds rowBounds = new RowBounds(1, 2); List<User> userList = sqlSession.selectList("com.kuang.dao.UserDao.getUserByRowBounds", null, rowBounds); for (User user : userList) { System.out.println(user); } sqlSession.close(); }
10.3 分页插件PageHelper(了解)
11. 使用注解开发
实现
- 底层使用了反射实现!
- 几乎在有参数的情况下,都需要加一个@Param,即传到SQL的参数名。
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper class="com.kuang.dao.UserDao"/>
</mappers>
@Select("select * from mybatis.user limit #{startIndex},#{pageSize}")
List<User> getUserByLimit(@Param("startIndex") int startIndex,@Param("pageSize") int pageSize);
@Test
public void getUserList(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> userByLimit = userDao.getUserByLimit(0,2);
System.out.println(userByLimit);
sqlSession.close();
}
@Param
- 基本类型或者String类型,都要加上@Param
- 引用类型不需要加
- 如果只有一个基本类型的话,可以忽略,但是还是必须加上,代码规范
- 在SQL中引用的就是@Param中的属性名!
#{}和${}
- #{}可以防止SQL注入
- 一 般 用 于 传 入 数 据 库 对 象 , 比 如 数 据 库 表 名 , 而 且 传 入 的 数 据 直 接 显 示 在 生 成 的 S Q L 中 , {}一般用于传入数据库对象,比如数据库表名,而且传入的数据直接显示在生成的SQL中, 一般用于传入数据库对象,比如数据库表名,而且传入的数据直接显示在生成的SQL中,{abc}直接显示abc
实现自动提交
//sqlSessionFactory -----> sqlSession
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
// 第一步:获取sqlSessionFactory对象!
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
// 创建sqlSession,SqLSession完全包含了面向数据库执行SQL命令所需的所有方法。
// 设置为true,实现自动提交
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
}
问题
使用注解开发无法解决字段名和实体类名不一致的问题!
12. Mybatis执行流程
13. 复杂查询(一对多多对一)
介绍
- 多个学生对应一个老师,即多对一。
- 对于学生这边而言,多个学生关联一个老师。
- 对于老师这边而言,一个老师集合很多学生。
环境搭建
-
数据库
CREATE TABLE `teacher` ( `id` INT(10) NOT NULL, `name` VARCHAR(30) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=INNODB DEFAULT CHARSET=utf8INSERT 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=utf8INSERT 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);
-
实体类:
-
学生
@AllArgsConstructor @NoArgsConstructor @Data public class Student { private int id; private String name; //学生关联一个老师 private Teacher teacher; }
-
老师
@AllArgsConstructor @NoArgsConstructor @Data public class Teacher { private int id; private String name; }
-
-
dao接口:StudentMapper、TeacherMapper
-
mapper文件:StudentMapper.xml、TeacherMapper.xml
13.1 多对一处理
需求
查出所有学生的信息和对应老师的信息
问题
<mapper namespace="com.kuang.dao.StudentMapper">
<select id="getStudent" resultType="student">
select s.id,s.name,t.name from student s,teacher t where s.tid = t.id
</select>
</mapper>
SQL语句在控制台验证正确,但是结果却错误!
思路
teacher是null,变相相当于是字段名和数据库名不一致问题,返回类型应该是resultmap
按照结果嵌套处理
<!--命名空间绑定一个对应的Dao接口-->
<mapper namespace="com.kuang.dao.StudentMapper">
<select id="getStudent" resultMap="StudentTeacher">
<!--起别名sid,sname,tname作为列-->
select s.id sid,s.name sname,t.id ttid,t.name tname from student s,teacher t where s.tid = t.id
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" column="tid" javaType="Teacher">
<result property="id" column="ttid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
</mapper>
按照过程嵌套处理
- 查询所有的学生
- 根据查询出所有学生的tid,寻找对应的id
<!--命名空间绑定一个对应的Dao接口-->
<mapper namespace="com.kuang.dao.StudentMapper">
<select id="getStudent" resultMap="StudentTeacher">
<!--起别名sid,sname,tname作为列-->
select * from student
</select>
<resultMap id="StudentTeacher" type="student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--如果所包含的是一个对象:用association,如果包含的是一个集合,用collections-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{tid}
</select>
</mapper>
13.2 一对多处理
比如:一个老师拥有多个学生!
对于老师而言,就是一对多的关系!
环境搭建
-
学生
@AllArgsConstructor @NoArgsConstructor @Data public class Student { private int id; private String name; private int tid; }
-
老师
@AllArgsConstructor @NoArgsConstructor @Data public class Teacher { private int id; private String name; //一个老师拥有多个学生 private List<Student> students; }
-
接口
public interface TeacherMapper { //获取指定老师下的所有学生及老师的信息 Teacher getTeacher(@Param("tid") int id); }
按结果嵌套查询
<!--命名空间绑定一个对应的Dao接口-->
<mapper namespace="com.kuang.dao.TeacherMapper">
<select id="getTeacher" resultMap="TeacherStudent">
select s.name sname,s.id sid,t.name tname,t.id tid
from student s,teacher t
where s.tid = t.id and t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
</mapper>
子查询方式(按照过程嵌套处理)
<!--命名空间绑定一个对应的Dao接口-->
<mapper namespace="com.kuang.dao.TeacherMapper">
<select id="getTeacher" resultMap="TeacherStudent">
select * from mybatis.teacher where id = #{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="name"/>
<collection property="students" ofType="Student" javaType="ArrayList" select="getStudent" column="id"/>
</resultMap>
<select id="getStudent" resultType="Student">
select * from mybatis.student where tid = #{tid}
</select>
</mapper>
13.3 小结
- 关联:association:多对一
- 集合:collection:一对多
- javaType:指定实体类中属性的类型
- ofType:用来指定映射到List或者集合中的pojo类型,泛型中的约束类型!
注意
- 保证SQL的可读性,尽量保证通俗易懂
- 注意一对多和多对一中属性名和字段的问题,如果问题不好排查,可以使用日志,建议log4j
14. 动态SQL
介绍
动态SQL就是根据不同条件生成不同的SQL语句
环境搭建
开启驼峰命名:
<?xml version="1.0" encoding="UTF8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>
-
实体类
@AllArgsConstructor @NoArgsConstructor @Data public class Blog { private int id; private String title; private String author; private Date createTime; private int views; }
-
接口
public interface BlogMapper { // 插入数据 int addBlog(Blog blog); }
-
Mapper.xml
<!--命名空间绑定一个对应的Dao接口--> <mapper namespace="com.kuang.dao.BlogMapper"> <insert id="addBlog" parameterType="blog"> insert into mybatis.blog(id,title,author,create_time,views) values (#{id},#{title},#{author},#{createTime},#{views}) </insert> </mapper>
-
测试
@Test public void TeacherTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); Blog blog = new Blog(); blog.setId(IdUtils.getId()); blog.setTitle("Mybatis"); blog.setAuthor("Q"); blog.setCreateTime(new Date()); blog.setViews(9999); blogMapper.addBlog(blog); blog.setId(IdUtils.getId()); blog.setTitle("Spring"); blogMapper.addBlog(blog); blog.setId(IdUtils.getId()); blog.setTitle("Java"); blogMapper.addBlog(blog); blog.setId(IdUtils.getId()); blog.setTitle("微服务"); blogMapper.addBlog(blog); sqlSession.close(); } }
14.1 IF语句
实现
-
不确定前端到底是通过什么样的值进行查询。title?author?
public interface BlogMapper { // 查询数据 List<Blog> getBlogByIf(Map map); }
-
mapper
<!--命名空间绑定一个对应的Dao接口--> <mapper namespace="com.kuang.dao.BlogMapper"> <insert id="addBlog" parameterType="blog"> insert into mybatis.blog(id,title,author,create_time,views) values (#{id},#{title},#{author},#{createTime},#{views}) </insert> <select id="getBlogByIf" parameterType="string" resultType="Blog"> select * from mybatis.blog where 1=1 <if test="biaoti != null"> and title = #{biaoti} </if> </select> </mapper>
select * from mybatis.blog where 1=1
是表示如果没有任何参数传进来,我们就查询所有的数据。<if test="biaoti != null">
表示如果有客户想通过标题查询,即biaoti这个参数(Map的键,客户搜索的值)传进来,那么在前面的SQL语句后拼接判断条件,即select * from mybatis.blog where 1=1 and title = #{biaoti}
。
-
测试查询
public class UserDaoTestTwo { @Test public void TeacherTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, Object> map = new HashMap(); map.put("biaoti","java"); // 访问查询接口穿过去的参数是biaoti,值是java List<Blog> blogs = blogMapper.getBlogByIf(map); System.out.println(blogs); sqlSession.close(); } }
14.2 常用标签
where
看一下下面的mapper中的SQL语句:
public class UserDaoTestTwo {
@Test
public void TeacherTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap();
// map.put("biaoti","java");
map.put("guankan",3333);
List<Blog> blogs = blogMapper.getBlogByIf(map);
System.out.println(blogs);
sqlSession.close();
}
}
在上面的语句中,如果我只传入guankan
参数作为查询条件,而不传入biaoti
,会出现什么问题呢?
public class UserDaoTestTwo {
@Test
public void TeacherTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap();
// map.put("biaoti","java");
map.put("guankan",3333);
List<Blog> blogs = blogMapper.getBlogByIf(map);
System.out.println(blogs);
sqlSession.close();
}
}
针对上面这种情况,mybatis推出了<where></where>
,他可以智能判断是否传入第一个查询参数,如果传入,会自动为后面的参数加入and
,如果不传入,会去除第二个参数的and
<!--命名空间绑定一个对应的Dao接口-->
<mapper namespace="com.kuang.dao.BlogMapper">
<insert id="addBlog" parameterType="blog">
insert into mybatis.blog(id,title,author,create_time,views)
values (#{id},#{title},#{author},#{createTime},#{views})
</insert>
<select id="getBlogByIf" parameterType="string" resultType="Blog">
select * from mybatis.blog
<where>
<if test="biaoti != null">
title = #{biaoti}
</if>
<if test="guankan!=null">
views = #{guankan}
</if>
</where>
</select>
</mapper>
choose
有时候我们不想应用到所有的条件语句,只想选择其中一项,那么用choose
相当于switch,when
相当于case
<mapper namespace="com.kuang.dao.BlogMapper">
<insert id="addBlog" parameterType="blog">
insert into mybatis.blog(id,title,author,create_time,views)
values (#{id},#{title},#{author},#{createTime},#{views})
</insert>
<select id="getBlogByIf" parameterType="string" resultType="Blog">
select * from mybatis.blog
<where>
<choose>
<when test="biaoti!=null">
title = #{biaoti}
</when>
<when test="guankan!=null">
views = #{guankan}
</when>
<otherwise>
author = #{zuozhe}
</otherwise>
</choose>
</where>
</select>
</mapper>
public class UserDaoTestTwo {
@Test
public void TeacherTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap();
map.put("biaoti","java");
map.put("guankan",5555);
map.put("zuozhe","QQQ");
List<Blog> blogs = blogMapper.getBlogByIf(map);
System.out.println(blogs);
sqlSession.close();
}
}
即使我们传入个三个参数,但它只会选择一个,默认是自上而下,因此结果是根据title查询的:
set
在更改某个数据的时候,我们通常使用 update xxx set a = #{a},b=#{b},如果第一个参数不传,逗号无法处理,在这个基础上,set可以完美解决。
<!--命名空间绑定一个对应的Dao接口-->
<mapper namespace="com.kuang.dao.BlogMapper">
<insert id="addBlog" parameterType="blog">
insert into mybatis.blog(id,title,author,create_time,views)
values (#{id},#{title},#{author},#{createTime},#{views})
</insert>
<select id="getBlogByIf" parameterType="string" resultType="Blog">
</select>
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="biaoti !=null">
title = #{biaoti},
</if>
<if test="guankan !=null">
views = #{guankan}
</if>
</set>
where id = #{idd}
</update>
</mapper>
所谓的动态sql,本质还是sql语句,只是我们可以在SQL层面,增加逻辑代码!
14.3 Foreach
SQL片段
有的时候,我们有很多复用的SQL片段,这个时候我们可以进行SQL的抽离和引用:
-
使用SQL标签
<sql></sql>
抽取公共部分<sql id="if-biaoti-guankan"> <if test="biaoti !=null"> title = #{biaoti}, </if> <if test="guankan !=null"> views = #{guankan} </if>
-
<include></include>
标签引用<!--命名空间绑定一个对应的Dao接口--> <mapper namespace="com.kuang.dao.BlogMapper"> <update id="updateBlog" parameterType="map"> update mybatis.blog <set> <include refid="if-biaoti-guankan"/> </set> where id = #{idd} </update> </mapper>
-
最好基于单边查询,且不要存在where标签
Foreach
-
mapper
<!--命名空间绑定一个对应的Dao接口--> <mapper namespace="com.kuang.dao.BlogMapper"> <select id="getBlogByForeach" parameterType="map" resultType="blog"> select * from mybatis.blog <where> <foreach collection="ids" item="item" open="and (" close=")" separator="or"> id = #{item} </foreach> </where> </select> </mapper>
-
测试:
public class UserDaoTestTwo { @Test public void TeacherTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, Object> map = new HashMap(); List<Integer> list = new ArrayList(); list.add(1); list.add(2); map.put("ids",list); List<Blog> blogs = blogMapper.getBlogByForeach(map); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close(); } }
-
动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了
-
建议先把sql完整写出来然后再用动态SQL拼接。
15. 缓存
简介
- MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
- MyBatis系统中状认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存,在close之前有效)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
15.1 一级缓存
简介
一级缓存也叫本地缓存:
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
测试
public class UserDaoTestTwo {
@Test
public void TeacherTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = blogMapper.getBlogByTitle("Java");
Blog blog2 = blogMapper.getBlogByTitle("Java");
System.out.println(blog2);
sqlSession.close();
}
}
缓存失效的原因
-
查询不同的东西
-
增删改操作,可能会改变原来的数据,所以必定会刷新缓存!(先查询1,再修改2,再次查询1,会进行三次SQL语句操作,而不是2次,缓存是针对整体数据库而言,而不是单个数据)
-
查询不同的Mapper.xml
-
手动清理缓存!
public class UserDaoTestTwo { @Test public void TeacherTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); Blog blog = blogMapper.getBlogByTitle("Java"); sqlSession.clearCache(); //手动清理缓存 Blog blog2 = blogMapper.getBlogByTitle("Java"); System.out.println(blog2); sqlSession.close(); } }
15.2 二级缓存
-
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存。
-
基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
-
工作机制:
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
- 新的会话查询信息,就可以从二级缓存中获取内容;
- 不同的mapper查出的数据会放在自己对应的缓存(map)中;
-
开启步骤:
-
开启全局缓存(默认是开启的,为了代码规范和可读性,我们需要显式开启让别人看到):
<?xml version="1.0" encoding="UTF8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!--核心配置文件--> <configuration> <settings> <!--显式开启全局缓存--> <setting name="cacheEnabled" value="true"/> </settings> </configuration>
-
在要使用二级缓存的mapper中开启:
<mapper namespace="com.kuang.dao.BlogMapper"> <!-- 在当前mapper.xml中使用二级缓存 --> <cache/> <select id="getBlogByTitle" resultType="Blog" parameterType="string"> select * from mybatis.blog where title = #{biaoti} </select> </mapper>
也可以自定义缓存参数:
<mapper namespace="com.kuang.dao.BlogMapper"> <!-- 在当前mapper.xml中使用二级缓存 --> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> <select id="getBlogByTitle" resultType="Blog" parameterType="string"> select * from mybatis.blog where title = #{biaoti} </select> </mapper>
-
测试:
<!--命名空间绑定一个对应的Dao接口--> <mapper namespace="com.kuang.dao.BlogMapper"> <!-- 在当前mapper.xml中使用二级缓存 --> <cache/> <select id="getBlogByTitle" resultType="Blog" parameterType="string"> select * from mybatis.blog where title = #{biaoti} </select> </mapper>
public class UserDaoTestTwo { @Test public void TeacherTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); SqlSession sqlSession2 = MybatisUtils.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); BlogMapper blogMapper2 = sqlSession2.getMapper(BlogMapper.class); Blog blog = blogMapper.getBlogByTitle("Java"); sqlSession.close(); Blog blog2 = blogMapper2.getBlogByTitle("Java"); System.out.println(blog2); sqlSession2.close(); } }
public class Blog implements Serializable { private String id; private String title; private String author; private Date createTime; private int views; }
-
小结
- 只要开启了二级缓存,在同一个Mapper下就有效。
- 所有的数据都会先放在一级缓存中,只有当会话提交,或者关闭的时候,才会提交到二级缓冲中!
15.3 自定义缓存ehcache
介绍
Ehcache是一种广泛使用的开源ava分布式缓存。主要面向通用缓存。
使用
-
导包
<dependencies> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1.0</version> </dependency> </dependencies> </project>
-
ehcache.xml
<?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 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"/> </ehcache>
-
测试,成功
public class UserDaoTestTwo { @Test public void TeacherTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); SqlSession sqlSession2 = MybatisUtils.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); BlogMapper blogMapper2 = sqlSession2.getMapper(BlogMapper.class); Blog blog = blogMapper.getBlogByTitle("Java"); sqlSession.close(); Blog blog2 = blogMapper2.getBlogByTitle("Java"); System.out.println(blog2); sqlSession2.close(); } }