Mybatis的基本概念和基本使用
说明:学习笔记是对B站狂神说的Mybatis视频的整理和总结。
参考资料:mybatis中文文档
创建第一个Mybatis项目
主要步骤:
- 导入依赖
- 编写Mybatis核心配置文件
- 编写工具类,即获取 SqlSession
- 编写实体类、接口、映射SQL的XML文件
- 测试功能
1、导入依赖
首先创建一个 Maven 项目,并在 pom.xml 文件中导入下面的依赖。
<dependencies>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</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>
</dependencies>
2、编写Mybatis核心配置文件
在resources文件夹下创建mybatis-config.xml文件作为Mybatis的核心配置文件。
- XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)
- dao包下的每一个Mapper接口都需要在Mybatis核心配置文件中注册。
<?xml version="1.0" encoding="GBK" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<!--可以配置多套环境,默认是development的环境-->
<environments default="development">
<environment id="development">
<!--JDBC的事务管理-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--每一个Mapper都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/xlq/dao/UserMapper.xml"/>
</mappers>
</configuration>
3、编写工具类,即获取 SqlSession
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 使用Mybatis的第一步,获取sqlSessionFactory对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
据 SqlSessionFactory 来获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
总结:
- XML配置文件→SqlSessionFactoryBuilder→SqlSessionFactory→SqlSession 注:中间步骤有所省略,详细参考Mybatis的执行流程。
- SqlSessionFactory和SqlSession的关系:SqlSessionFactory可以理解成一个连接池,且全局环境下只需要一个连接池就够了,SqlSession可以理解出一个数据库连接,连接可以有多个,但用完之后一定要记得关闭,不然会一直占用资源。
4、编写实体类、接口、映射SQL的XML文件
实体类:
public class User {
// 与数据库中的字段一一对应
private int id;
private String name;
private String pwd;
// 构造方法,get方法,set方法,toString方法这里省略
}
接口:
public interface UserMapper {
// 查询全部用户
List<User> getUserList();
}
映射SQL的XML文件:
<?xml version="1.0" encoding="GBK" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace就是绑定一个对应的Mapper接口-->
<mapper namespace="com.xlq.dao.UserDao">
<!--select查询语句-->
<select id="getUserList" resultType="com.xlq.pojo.User">
select * from test.user
</select>
</mapper>
5、测试功能
@Test
public void test() {
// 第一步:获得SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 方式一:getMapper
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> userList = userDao.getUserList();
for (User user : userList) {
System.out.println(user);
}
// 关闭SqlSession
sqlSession.close();
}
测试过程中遇到的错误:
错误1:org.apache.ibatis.binding.BindingException: Type interface com.xlq.dao.UserDao is not known to the MapperRegistry.
分析:每一个Mapper都需要在Mybatis核心配置文件中注册。在mybatis-config.xml 中加入如下注册:
<mappers>
<mapper resource="com/xlq/dao/UserDao"/>
</mappers>
错误2:java.lang.ExceptionInInitializerError
分析:这是资源导出失败的问题,即UserMapper.xml被过滤掉了。maven由于它的约定大于配置,配置文件可能会出现无法被导出或者失效的问题。解决方法是在maven的配置文件pom.xml中加入下面的资源过滤规则:
<!--在build中配置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>
可能会发生的问题:
- 配置文件没有注册;
- 绑定接口错误;
- 方法名不对;
- 返回类型不对;
- Maven导出资源问题。
补充:MyBatis的详细执行流程
CURD-增删改查
基本步骤:
- 编写接口
- 编写对应的mapper.xml中的sql语句
- 测试(注意:增删改需要提交事务sqlSession.commit())
CURD示例:
<!--namespace就是绑定一个对应的Mapper接口-->
<mapper namespace="com.xlq.mapper.UserMapper">
<!--select查询语句-->
<select id="getUserList" resultType="com.xlq.pojo.User">
select * from test.user
</select>
<select id="getUserById" parameterType="int" resultType="com.xlq.pojo.User">
select * from test.user where id=#{id}
</select>
<insert id="addUser" parameterType="com.xlq.pojo.User">
insert into test.user(id, name, pwd) values (#{id}, #{name}, #{pwd})
</insert>
<update id="updateUser" parameterType="com.xlq.pojo.User">
update test.user set name=#{name}, pwd=#{pwd} where id=#{id}
</update>
<delete id="deleteUser" parameterType="int">
delete from test.user where id=#{id}
</delete>
</mapper>
1、标签的说明:
<mapper>
标签的参数:- namespace绑定一个Mapper接口的全限定名。
- sql语句的标签
<select><insert><update><delete>
的参数说明:- id:对应的是namespace中的方法名。
- resultType:SQL语句执行的返回值。
- parameterType:参数类型。
2、参数传递的方式
- Map传递参数,直接在sql中取出key即可。
- 对象传递参数,直接在sql中取对象的属性即可!
- 只有一个基本类型参数的情况下,可以直接在sql中取到。
- 多个参数用Map,或者注解。
关于Map传参:适用于实体类或者数据库中的表字段非常多的情况。示例如下:
int addUser2(Map<String, Object> map);
<!--通过map来传递数据,适用于字段非常多的情况。传递map的key-->
<insert id="addUser2" parameterType="map">
insert into test.user(id, name, pwd) values (#{userid}, #{name}, #{password})
</insert>
public void addUser2() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("userid", 4);
map.put("password", 123);
map.put("name", "赵四");
mapper.addUser2(map);
sqlSession.commit();
sqlSession.close();
}
3、模糊查询LIKE
Java代码执行的时候,用户传递通配符。但是可能会存在SQL注入的问题,即用户的非法输入改变了SQL语句的含义。
List<User> userList = mapper.getUserLike("%李%");
另一种方式,在sql拼接中使用通配符,这样可以限定用户的输入。
<select id="getUserLike" resultType="com.xlq.pojo.User">
select * from test.user where name Like "%"#{value}"%"
</select>
配置解析
官网资料:Mybatis配置
mybatis-config.xml文件
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
需要掌握:属性、设置、类型别名、环境配置、映射器。
1 环境配置(environments)
MyBatis 可以配置成适应多种环境,尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
Mybatis默认的事务管理器是JDBC,默认的数据源:连接池POOLED
2 属性(properties)
我们可以通过properties属性来实现引用配置文件。
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。
编写配置文件db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456
在mybatis-config.xml中引入外部配置文件,将外部配置文件中设置好的属性就可以在整个配置文件中用来替换需要动态配置的属性值。
<!--引入外部配置文件-->
<properties resource="db.properties"/>
<!--在环境中动态配置数据源-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
注意点:
- 可以直接引入外部配置文件
- 可以在其中增加一些属性配置
- 如果出现同名的属性,优先使用外部配置文件的属性。
3 设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
三个关键设置:
4 类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
第一种方式:直接指定某个类及其别名是什么。
<typeAliases>
<typeAlias type="com.xlq.pojo.User" alias="User"/>
</typeAliases>
第二种方式:指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean(也就是指某个类)。在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名;若有注解,则别名为其注解值。
<typeAliases>
<package name="com.xlq.pojo"/>
</typeAliases>
5 映射器(mappers)
MapperRegistry:注册绑定我们的Mapper文件。
主要有三种方式:
方式一:使用相对于类路径的资源引用【推荐】
<mappers>
<mapper resource="com/xlq/dao/UserMapper.xml"/>
</mappers>
方式二:使用映射器接口实现类的完全限定类名
<mappers>
<mapper class="com.xlq.dao.UserMapper"/>
</mappers>
方式三:将包内的映射器接口实现全部注册为映射器
<mappers>
<package name="com.xlq.dao"/>
</mappers>
方式二和方式三的注意点:
- 接口和它的Mapper配置文件必须同名!
- 接口和它的Mapper配置文件必须在同一个包下!
6 生命周期和作用域
不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder:
- 一旦创建了 SqlSessionFactory,就不再需要它了
- 局部变量
SqlSessionFactory:
- 可以理解为:数据库连接池。
- SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- SqlSessionFactory 的最佳作用域是应用作用域。
- 最简单的就是使用单例模式或者静态单例模式。
SqlSession:
- 可以理解为:连接到连接池的一个请求;
- SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
- 用完之后需要赶紧关闭,否则资源被占用。
XML映射
问题引入:当实体类中的属性和数据库中的字段名称不一致时,类型处理器无法找到对应的映射,所以结果为null。比如实体类User中的属性是password,数据库中的字段为pwd.
最简单暴力的解决方法:在SQL语句中写别名。
<select id="getUserList" resultMap="User">
select id, name, pwd as password from test.user
<!--User是实体类,test是数据库,user是表名,id name pwd是表中的字段-->
</select>
结果映射(resultMap)
要点:
- resultMap 元素是 MyBatis 中最重要最强大的元素。
- resultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
- resultMap 的优秀之处——你完全可以不用显式地配置它们。
<resultMap id="userMap" type="User">
<!--对于id name不需要显示配置,只需要配置pwd就可以了,这就是resultMap的优秀之处-->
<result column="pwd" property="password"/>
</resultMap>
<select id="getUserList" resultMap="userMap">
select * from test.user
</select>
日志
Mybatis支持的日志包括:
- SLF4J
- LOG4J【掌握】
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING【掌握】
- NO_LOGGING
其中STDOUT_LOGGING是Mybatis的标准日志输出。
在mybatis-config.xml中加入如下配置:
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
IDEA的控制台中日志输出内容部分如下:
log4j
什么是log4j?有何特点?
- log4j是Apache的一个开源项目,通过使用log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件。
- 我们也可以控制每一条日志的输出格式。
- 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
- 这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
第一步:添加依赖。
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
第二步:配置
配置log4j的配置文件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=./logInfo/xlq.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=DUBUG
log4j.logger.java.sql.Statement=DUBUG
log4j.logger.java.sql.ResultSet=DUBUG
log4j.logger.java.sql.PreparedStatement=DUBUG
第三步:在mybatis核心配置文件中设置日志为log4j
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
第四步:在项目中使用log4j,产生日志文件。
- 在要使用log4j的类中,导入包:
import org.apache.log4j.Logger;
- 生成日志对象,参数为当前类的class
static Logger logger = Logger.getLogger(UserMapperTest.class);
- 日志级别:
logger.info("info:进入了testLog4j方法");
logger.debug("debug:进入了testLog4j方法");
logger.error("error:进入了testLog4j方法");
分页
为什么要分页?减少数据量的处理量。
使用Mybatis实现分页的功能,核心在于SQL,limit子句实现分页的功能。
SQL语句:
// 如果只给定一个参数,它表示返回最大的记录行数目。换句话说,LIMIT n 等价于 LIMIT 0,n:
mysql> SELECT * FROM table LIMIT 5; //检索前 5 个记录行
// 如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参数指定返回记录行的最大数目。
mysql> SELECT * FROM table LIMIT 5,10; //检索记录行6-15
Mapper.xml
<!--分页-->
<select id="getUserListLimit" parameterType="map" resultMap="userMap">
select * from test.user limit #{bias}, #{num}
</select>
测试:
@Test
public void testLimit() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("bias", 2);
map.put("num", 2);
List<User> userList = userMapper.getUserListLimit(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
第二种思路:不使用SQL来实现,而是通过Java代码层面来实现分页(了解)
RowBounds类 -> SqlSession.selectList()
注解开发
面向接口编程
面向对象编程 VS 面向接口编程
在真正的开发中,很多时候我们会选择面向接口编程。根本原因:解耦。分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好。
对接口的理解:
- 接口是定义(规范、约束)与实现的分离。
- 接口的本身反映了系统设计人员对系统的抽象理解。
- 接口应有两类:
- 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class)。
- 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface)。
使用注解开发
1、在Mapper中使用注解,代替了XML文件了。
public interface UserMapper {
@Select("select * from user")
List<User> getUserList();
}
注意:简单的SQL可以使用注解,复杂的SQL就力不从心了。
2、在Mybatis核心配置文件中绑定接口:
<!--绑定接口-->
<mappers>
<mapper class="com.xlq.dao.UserMapper"/>
</mappers>
3、测试–略
CURD
public interface UserMapper {
@Select("select * from user")
List<User> getUserList();
@Select("select * from user where id = #{uid}")
User getUserById(@Param("uid") int id);
@Insert("insert into user(id, name, pwd) values (#{id}, #{name}, #{pwd})")
void InsertUser(User user);
@Update("update user set name=#{name}, pwd=#{pwd} where id=#{id}")
void updateUser(User user);
@Delete("delete from user where id=#{uid}")
void deleteUser(@Param("uid") int id);
}
多对一和一对多
复杂查询的准备工作
在MySQL数据库中创建老师表和学生表,并且使用外键产生字段关联。
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) NOT 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);
在IDEA中做如下准备:
- 新建实体类:Teacher,Student,并生成构造方法,get和set方法。(使用lombok很方便,自己不太喜欢用)
- 建立对应的Mapper接口:TeacherMapper,StudentMapper
- 建立Mapper.xml文件
- 在mybatis核心配置文件中绑定注册Mapper接口
- 测试环境配置是否正常
多对一关系
什么叫多对一?
多个学生关联一个老师,简单来说就是多对一。具体在数据库表中,学生表中有一个字段tid
对应的是老师表中的id
字段。
实体类:
public class Student {
private int id;
private String name;
// 学生需要关联一个老师
private Teacher teacher;
}
public class Teacher {
private int id;
private String name;
}
MySQL中多对一的查询方式:
- 子查询
- 连表查询
对应到XML中,子查询对应【按照查询嵌套处理】,连表查询对应【按照结果嵌套处理】。也就是说用子查询的思想去看【按照查询嵌套处理】中的XML代码,用连表查询的思想去看【按照结果嵌套处理】的XML代码。
1、按照查询嵌套处理
<!--直接查询student表,属性teacher是null,因为它是引用变量-->
<!--思路:
1.查询所有的学生信息
2.根据查询出来的学生的tid,寻找对应的老师(即子查询)-->
<select id="getStudent" resultMap="StudentTeacher">
select * from test.student
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--复杂的属性,我们需要单独处理,对象:association 集合:collection-->
<association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="teacher">
select * from test.teacher where id=#{tid}
</select>
查询出来的结果是:
Student{id=1, name='小明', teacher=Teacher{id=1, name=王老师}}
Student{id=2, name='小红', teacher=Teacher{id=1, name=王老师}}
Student{id=3, name='小张', teacher=Teacher{id=1, name=王老师}}
Student{id=4, name='小李', teacher=Teacher{id=1, name=王老师}}
Student{id=5, name='小芳', teacher=Teacher{id=1, name=王老师}}
2、按照结果嵌套处理
<!--方式二:按照结果嵌套处理-->
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id as sid, s.name as sname, t.name as tname
from test.student as s, test.teacher as t
where s.tid = t.id
</select>
<resultMap id="StudentTeacher2" type="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
得到的结果是:
Student{id=1, name='小明', teacher=Teacher{id=0, name=王老师}}
Student{id=2, name='小红', teacher=Teacher{id=0, name=王老师}}
Student{id=3, name='小张', teacher=Teacher{id=0, name=王老师}}
Student{id=4, name='小李', teacher=Teacher{id=0, name=王老师}}
Student{id=5, name='小芳', teacher=Teacher{id=0, name=王老师}}
注意和上面的结果的差别:Teacher的id都为零,因为代码中没有做映射。
一对多关系
什么是一对多?
一个老师拥有多个学生,对于老师而言,就是一对多的关系。所以说一对多和多对一是站在不同的角度来看的。
实体类:
public class Student {
private int id;
private String name;
private int tid;
}
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
1、按结果嵌套查询
<select id="getTeacher" parameterType="int" resultMap="teacherStudent">
select s.id as sid, s.name as sname, t.id as tid, t.name as tname
from teacher as t, student as s
where t.id=s.tid and t.id=#{tid}
</select>
<!--type="teacher"和ofType="student",可以用teacher和student是因为取了别名-->
<resultMap id="teacherStudent" type="teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--复杂的属性,我们需要单独处理,对象:association 集合:collection-->
<!--javaType用于指定属性的类型,ofType用于指定集合中的泛型信息-->
<collection property="students" ofType="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
查询结果:
Teacher{id=1, name='王老师', students=[Student{id=1, name='小明', tid=1}, Student{id=2, name='小红', tid=1}, Student{id=3, name='小张', tid=1}, Student{id=4, name='小李', tid=1}, Student{id=5, name='小芳', tid=1}]}
2、按查询条件
<select id="getTeacher2" parameterType="int" resultMap="teacherStudent2">
select * from test.teacher where id=#{tid}
</select>
<resultMap id="teacherStudent2" type="teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students" javaType="ArrayList" ofType="student" select="getStudentByTeacherId" column="id"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="student">
select * from test.student where tid=#{tid}
</select>
查询结果和上面的一样。
小结
- 关联 - association 【多对一】
- 集合 - collection 【一对多】
- javaType和ofType区别:
- javaType用来指定实体类中属性的类型。也就说实体类的某个属性可能是引用类型,javaType就用来指定这个引用类型。注意:这个引用类型肯定是另外一个实体类,也在bean中注册了。
- ofType用来指定映射到List或者集合中的实体类型,简单理解就是泛型中的约束类型。
注意点:
- 保证SQL的可读性,尽量保证通俗易懂
- 注意一对多和多对一中属性名字和字段的问题
- 如果不好排查错误,可以使用日志。
动态SQL
什么是动态SQL?动态SQL就是指根据不同的条件生成不同的SQL语句。
环境搭建
环境搭建的过程包括:Mybatis核心配置、工具类、实体类、实体类的Mapper接口和Mapper.xml、在测试类中写代码往数据库中插入数据。
MySQL中的blog表建表语句:
CREATE TABLE `blog` (
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8
实体类:
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;// 属性名和字段名不一致
private int views;
}
实体类的Mapper接口,暂时只包含一个方法,即往数据库中插入数据。
public interface BlogMapper {
void insertBlog(Blog blog);
}
Mapper对应的XML文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xlq.dao.BlogMapper">
<insert id="insertBlog" parameterType="com.xlq.pojo.Blog">
insert into test.blog (id, title, author, create_time, views)
values (#{id}, #{title}, #{author}, #{createTime}, #{views})
</insert>
</mapper>
动态SQL常用标签
最常用的标签:if where set
1、IF
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from test.blog where
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
<!--注:if标签之间是并列的关系,每一个都会去匹配-->
</select>
这样会有一个问题:如果只有第二个<if>
标签满足,那么SQL语句变成了select...where and author=#{author}
,where后面跟了and,显然是不符合语法规范的。解决方法:
第一种:where后面添加1=1
。
select * from test.blog where 1=1
and title = #{title}
and author = #{author}
第二种:使用<where>
标签,它会智能地去除and
。
2、choose(when、otherwise)
类似于Java中的switch-case语句,只选择其中一个执行,如果没有匹配到就会进入<otherwise>
标签中。
3、where
where元素只会在至少有一个子元素的条件返回SQL子句的情况下,才去插入“WHERE”子句,而且,若语句的开头为“AND”或“OR”,where元素也会将它们去除。
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from test.blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="views != null">
and views = #{views}
</if>
</where>
</select>
4、set
用于update语句中,<set>
元素会动态前置SET关键字,同时也会删掉无关的逗号。
解释:因为<set>
标签里面用的是<if>
标签,如果最后一个<if>
没有匹配上而前面的匹配上了,SQL语句中就会遗留一个逗号。<set>
就可以删除这个遗留的逗号。
了解:<where>
和<set>
有一个父标签<trim>
。
5、foreach
遍历集合。
<select id="selectBlogForeach" parameterType="map" resultType="blog">
select * from test.blog
<where>
<foreach collection="list" item="id"
open="(" separator="or" close=")">
id=#{id}
</foreach>
</where>
</select>
测试类:
@Test
public void test3() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
map.put("list", list);
List<Blog> blogs = mapper.selectBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
6、SQL片段
作用是实现代码的复用。使用<sql>
标签抽取公共的部分,在需要使用的地方使用<include>
标签引用即可。
<sql id="if-title-views">
<if test="title != null">
title = #{title}
</if>
<if test="views != null">
and views = #{views}
</if>
</sql>
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from test.blog
<where>
<include refid="if-title-views"/>
</where>
</select>
小结:所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码,实现SQL的动态拼接。
缓存
缓存的概念
1、什么是缓存?
- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,直接从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
2、为什么要使用缓存?
减少和数据库的交互次数,减少系统开销,提高系统效率。
3、什么样的场景下使用缓存?
经常查询并且不经常改变的数据。
Mybatis缓存
Mybatis系统中默认定义了两极缓存:一级缓存和二级缓存。
- 默认情况下,只有一级缓存开启(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,它是基于namespace级别的缓存。
- 为了提高扩展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存
1、一级缓存
一级缓存也叫做本地缓存:
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。
测试代码:
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectUserById(1);
System.out.println(user);
User user1 = mapper.selectUserById(1);
System.out.println(user1);
System.out.println("user==user1 ? "+ (user == user1));
sqlSession.close();
}
结果如下,可以看出第二次查询ID为1的用户是直接得到结果的,并没有从数据库中查询,而且两次查出来的用户是同一个用户。
Opening JDBC Connection
Created connection 811760110.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@306279ee]
==> Preparing: select * from test.user where id=?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 王二, 123456
<== Total: 1
User{id=1, name='王二', pwd='123456'}
User{id=1, name='王二', pwd='123456'} // 这里是第二次查询ID为1的用户
user==user1 ? true // 两次查出来的用户是同一个用户
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@306279ee]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@306279ee]
Returned connection 811760110 to pool.
2、二级缓存
- 由于一级缓存作用域太低了,所以诞生了二级缓存。
- 二级缓存也叫全局缓存,这里的【全局】指的是命名空间,所以二级缓存是基于NameSpace级别的缓存。
- 二级缓存的工作机制:
- 转存的概念:一个会话查询的数据保存在当前会话的一级缓存中,如果当前会话关闭了,这个会话对应的一级缓存就没了,但是查询的数据会被转存到二级缓存中。
- 新的会话查询信息,就可以从二级缓存中获取数据。
- 不同的Mapper查出的数据会放在自己对应的二级缓存中。
使用二级缓存的步骤:
第一步:在mybatis核心配置文件中显式开启全局缓存:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
注:"cacheEnabled"的默认值就是true,但是在这里再显示地写出来,表明开启全局缓存。
第二步:在要使用二级缓存的Mapper中开启缓存:
<mapper namespace="com.xlq.dao.UserMapper">
<cache/>
</mapper>
还可以自定义缓存参数:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
意思是创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的。
第三步:测试
先开启一个SqlSession进行查询ID为1的用户,然后关闭连接;再开启另外一个SqlSession,查询同一个用户,结果表明:第二次查询是直接在缓存中读取,并没有到数据库中查询。
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectUserById(1);
System.out.println(user);
sqlSession.close();
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = mapper1.selectUserById(1);
System.out.println(user1);
System.out.println("user==user1 ? "+ (user == user1));
sqlSession1.close();
}
日志打印结果:
Opening JDBC Connection
Created connection 1434041222.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5579bb86]
==> Preparing: select * from test.user where id=?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 王二, 123456
<== Total: 1
User{id=1, name='王二', pwd='123456'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5579bb86]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5579bb86]
Returned connection 1434041222 to pool.
Cache Hit Ratio [com.xlq.dao.UserMapper]: 0.5
User{id=1, name='王二', pwd='123456'}
user==user1 ? true
可能会出现的问题:如果在Mapper.xml中直接设置缓存<cache/>
,会报错:
Cause: java.io.NotSerializableException: com.xlq.pojo.User
原因是实体类没有实现序列化接口。因为要把这个对象给缓存到内存中,就必须要能够保存这个对象,就要实现序列化接口。
3、自定义缓存(了解即可)
Ehcache:是一种广泛使用的开源Java分布式缓存,主要面向通用缓存。
自定义缓存:自定义缓存类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器。(与数据库底层相关,较为复杂)
目前主流是Redis作为缓存数据库。
缓存的原理
缓存顺序:
- 先看二级缓存中有没有数据
- 再看一级缓存中有没有数据
- 都没有,再查询数据库