MyBatis
MyBatis
什么是MyBatis
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了[google code](https://baike.baidu.com/item/google code/2346604),并且改名为MyBatis 。2013年11月迁移到Github。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)
框架的诞生是为了方便程序员的,可以简化代码和方便项目管理。
官方文档:https://mybatis.org/mybatis-3/zh/index.html
特点
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
- 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql。
第一个Mybatis程序
项目结构图

配置环境-->编写代码-->测试代码
配置环境
- 新建一个普通的Maven项目
- 将改项目下的src目录删除,然后新建子模块。
- 在父级模块的pom.xml中导入依赖
<dependencies>
<dependency>
<!--Java连接数据库支持-->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<!--Mybatis框架依赖-->
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.0-M1</version>
<scope>test</scope>
</dependency>
</dependencies>
- 配置资源导出路径
在父级项目的pom.xml文件中添加:
<!--资源导出路径,不配置的话,在用流的时候可能找不到资源文件-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
编写代码
- 编写实体类
package com.bin.pojo;
public class User {
private int id;
private String username;
private String pwd;
public User() {
}
public User(int id, String username, String pwd) {
this.id = id;
this.username = username;
this.pwd = pwd;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
- 编写工具类
package com.bin.utils;
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 MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static{
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//获得SQLSession对象
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
- 编写核心配置文件
<?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"/>
<!--URL-->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false" />
<!--用户名-->
<property name="username" value="root"/>
<!--密码-->
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/bin/mapper/UserMapper.xml"/>
</mappers>
</configuration>
-
编写Mapper
- UserMapper接口
package com.bin.mapper; import com.bin.pojo.User; import java.util.List; public interface UserMapper { public List<User> getUserList(); }- 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.bin.mapper.UserMapper"> <!--id:方法名,resultType:指定泛型,注意用全限类名--> <select id="getUserList" resultType="com.bin.pojo.User"> <!--具体的SQL语句--> select * from user; </select> </mapper>写完UserMapper.xml后,别忘了在核心配置文件中mappers处添加对于的mapper。
测试程序
- Test.java
package com.bin;
import com.bin.mapper.UserMapper;
import com.bin.pojo.User;
import com.bin.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class Test {
@org.junit.Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
//记得关闭,非常重要!!!
sqlSession.close();
}
}
测试结果

可能会出现的异常
- java.io.IOException: Could not find resource mybatis-config
这是折磨了我一天的异常,我真的是服了,网上找了很多方法都不管用,最后才发现自己的核心配置文件没有用xml格式储存

心态有点小崩。。。。
- java.lang.ExceptionInInitializerError
这个异常大多情况下是配置文件写的有问题,不管是Mapper.xml还是核心配置文件,根据下面提示定位修改即可。
CRUD
UserMapper----->UserMapper.xml----->测试类
Mapper.xml文件
- namespace:命名空间,放接口的全限类名
- id:放方法名
- resultType:返回值类型,如果是集合则返回集合指定的泛型,全限类名
- parameterType:参数类型,如果是放实体类,需要提供全限类名
CRUD
增
- UserMapper
int insertUser(User user);
- UserMapper.xml
<insert id="insertUser" parameterType="com.bin.pojo.User">
<!--#{}用该表达式来传参-->
insert into user (id,username,pwd) values(#{id},#{username},#{pwd})
</insert>
- 测试类
public class Test {
@org.junit.Test
public void insertTest() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.addUser(new User(4,"赵六","123456"));
//提交事务
sqlSession.commit();
//记得关闭
sqlSession.close();
}
删
- UserMapper
int deleteUser(int id);
- UserMapper.xml
<insert id="deleteUser" parameterType="int">
<!--#{}用该表达式来传参-->
delete from user where id = #{id}
</insert>
- 测试类
public class Test {
@org.junit.Test
public void deleteUser() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUser(1);
//提交事务
sqlSession.commit();
//记得关闭
sqlSession.close();
}
改
- UserMapper
int updateUser(User user);
- UserMapper.xml
<update id="updateUser" parameterType="com.bin.pojo.User">
update user set username = #{username},pwd = #{pwd} where id = #{id}
</update>
- 测试类
public class Test{
@org.junit.Test
public void test() {
SqlSession sqlsession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlsession.getMapper(UserMapper.class);
mapper.updateUser(new User(1,"李四","122334"));
//提交事务
sqlsession.commit();
//关闭连接
sqlsession.close();
}
}
注意事项
- 用使用增删改时,记得提交事务,否则数据库中的数据不会得到更新。
万能的Map
每次用实体类作为参数调用dao层的方法有时会显得比较笨拙,比如说:我想通过id来修改用户的密码,如果是用实体类作为参数传入的话,还需要附带一些其他没用的字段/属性,生日,注册时间,年龄。。。。这些跟业务并没有什么联系,想实现的业务仅仅是涉及到两个字段/属性而已,如果是这种情况,可以使用万能的Map,将Map对象作为参数传入,将所需要的字段/属性放作为Key值放入该Map即可。
- UserMapper
int updatePassword(Map<String,Object> map);
- UserMapper.xml
<update id="updatePassword" parameterType="map">
update user set pwd = #{pwd} where id = #{id}
</update>
- 测试类
@org.junit.Test
public void updatePassword(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
Map<String, Object> map = new HashMap<String, Object>();
map.put("id",2);
map.put("pwd","123456");
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updatePassword(map);
sqlSession.commit();
sqlSession.close();
}
模糊查询
查询一个名字带有张的人,如何实现?
- UserMapper
List<User> getUserListByLike(String username);
- UserMapper.xml
<select id="getUserListByLike" parameterType="string" resultType="hello">
select * from user where username like #{username}
</select>
- 测试类
@org.junit.Test
public void getUserListByLike() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//关键代码,在字符串中首尾嵌入两个“%”
List<User> userList = mapper.getUserListByLike("%张%");
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
占位符
占位符会出现在Mapper.xml文件中的sql语句
{}里面填法:
- 传参传的是单个基本数据类型:使用该变量名
- 传参传的是对象:该对象的属性
- 传参传的是Map对象:该Map中的Key
配置解析
核心配置文件
顺序
不按照特定顺序编写核心配置文件会报以下异常:
Error building SqlSession.
Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance. Cause: org.xml.sax.SAXParseException; lineNumber: 26; columnNumber: 17; 元素类型为 "configuration" 的内容必须匹配 "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)"。
也就是表明了,核心配置文件必须按照
properties-->settings-->typeAliases-->typeHandlers-->objectFactory-->objectWrapperFactory-->reflectorFactory-->plugins-->environments-->databaseIdProvider-->mappers
顺序进行配置
环境配置
- evironments:
可以配置多个环境,但是每个SQLFactory每次只能选择一个环境,可以通过environments中的default属性来选择相应的环境。
<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>
<environment id="development2">
<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>
- 事务管理transactionManager
type="[JDBC|MANAGED]"
- 数据源dataSource
type="[UNPOOLED|POOLED|JNDI]"
属性properties
可以通过外部的properties文件来对核心配置文件中的相关属性进行配置。
- mybatis-config.xml
<properties resource="db.properties" />
- db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&allowPublicKeyRetrieval=true
username=root
password=root
- mybatis-config.xml
<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>
类型别名
在使用Mapper.xml文件时,有时需要用到实体类的权限类名,比如:resultType="com.bin.pojo.User",但每次这样写会显得过于冗余,为此可以在核心配置文件中引入别名。
typeAliases
- typeAlias
<typeAliases>
<typeAlias alias="hello" type="com.bin.pojo.User" />
</typeAliases>
- package(推荐使用)
<package name="com.bin.pojo"/>
使用包名,可以将特定包下的类按照一定规则起别名,实体类:若没有用别名注解声明别名,则别名为首字母小写的实体类类名,若有则按照注解声明的别名。
- 使用场景
在实体类较少时可以用typeAlias,实体类较多可以用package。
映射器
映射器的作用是告诉Mybatis去哪里能找到Mapper.xml,每写一个Mapper.xml,都要有去核心配置文件配置的习惯。
mappers
- resource属性
<mapper resource="com.bin.dao.UserMapper.xml"/>
这是一种较为官方的做法。。。
- 将特定包内的映射器接口实现全部注册为映射器
<package name="org.mybatis.builder"/>
映射器接口实现过多的情况,用该方法好像挺不错的
- 使用映射器接口实现类的完全限定名(推荐)
<mapper class="com.bin.dao.UserMapper"/>
使用该方法,需要满足以下两个要求
1.映射器名字需要跟所实现的接口名一致
2.映射器与对应接口需在同一个包下
生命周期和作用域
不同的作用域和生命周期类别是十分重要的,如果错误的使用会导致很严重的并发问题。

- SqlSessionFactoryBuilder
该类可以被实例化、使用和丢弃,一旦创建了SQLSessionFactory就不需要它了,因此SqlSessionFactoryBuilder最合适的作用域是方法作用域,可以用SQLSessionFactoryBuilder来创建多个SqlSessionFactory,但是一旦创建完,就不需要保留它了
- SqlSessionFactory
SqlSessionFactory一旦被创建就应该一直存在,没有理由丢弃或者去创造另一个实例,因此最合适的作用域是应用作用域,使用SQLSessionFactory最佳的实践方法就是在应用运行期间不要被创建多次,最简单的实现方案是单例模式和静态单例模式。
- SqlSession
每个线程都应该有自己的SQLSession实例,SQLSession是非线程安全的,因此是不能被共享的,因此最佳的作用域是方法作用域,绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。用完及时关闭

解决问题
属性名和字段名不一致的问题
首先实体类与数据库中的表是一一对应的。
表名-->实体类的类名
字段名-->实体类的属性名
但是也会遇到这种情况,数据库中的字段有时会倾向于以这种方式命名create_date,而在实体类中采用驼峰命名法,createDate。
先测试一下属性名与字段名不一致会出现什么问题。
- 实体类
实体类中的密码:private String password
- 数据库
数据库表中的字段:pwd
确定一下表中信息是否正常:

- getUserList()
@org.junit.Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}

解决方案
- 取别名
select id,username,pwd as password from user;
- resultMap
<resultMap id="resultMap1" type="user">
<result column="pwd" property="password"></result>
</resultMap>
<select id="getUserList" resultMap="resultMap1">
select * from user
</select>
<select id="getUserList" resultMap="resultMap1">
select * from user
</select>
注意
1.当实体类中只是存在个别属性名与表中字段名不一致的情况,只需要将不一致的属性和字段说明映射关系即可。
2.resultMap 元素是 MyBatis 中最重要最强大的元素
日志
如果一个数据库操作出现了异常,我们就需要排错,日志就是最好的帮手,以前:sout,debug。现在:日志工厂
日志工厂

- SLF4J
LOG4J- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
STDOUT_LOGGING- NO_LOGGING
STDOUT_LOGGING
这个使用较为简单,直接修改核心配置文件即可
<settings>
<setting name="logImpl" value="STUDOUT_LOGGING"></setting>
</settings>

log4j
简介:
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等.
- 最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
使用:
- 导入依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 在resources目录下新建一个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/bin.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
- 在mybatis核心配置文件中设置相关属性
<settings>
<!--注意大小写和空格,一个都不能错-->
<setting name="logImpl" value="LOG4J"/>
</settings>
- 测试


简单使用
之前做测试时一直是使用的System.out.println()来测试,现在可以用新的方式。
public class Test01{
private Logger logger = Logger.getLogger(Test.class);
@Test
public void log4jTest() {
logger.info("info:进入了log4jTest");
logger.debug("debug:进入了log4jTest");
logger.error("error:进入了log4jTest");
}
}

分页
limit实现
- UserMapper
List<User> getUserListLimit(Map<String,Integer> map);
- UserMapper.xml
<select id="getUserListLimit" parameterType="map" resultType="user">
select * from user limit #{startIndex},#{pageSize}
</select>
- 测试类
@Test
public void getUserListLimitTest() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Integer> map = new HashMap(String,Integer);
map.put("startIndex",1);
map.put("pageSize",3);
List<User> userList = mapper.getUserListLimit(map);
for(User user:userList){
System.out.println(user);
}
sqlSession.close();
}
PageHelper插件

以后可能会用到,了解有这个东西存在即可。
用注解进行CRUD
面向接口编程
- 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
- 根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好
- 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
- 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
关于接口的理解
- 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
- 接口的本身反映了系统设计人员对系统的抽象理解。
- 接口应有两类:
- 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
- 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
- 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。
三个面向区别
- 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法 .
- 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现 .
- 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题.更多的体现就是对系统整体的架构
使用注解进行CRUD
接口--->配置文件--->测试类
- UserMapper
public interface UserMapper {
//增
@Insert("insert into user(id,username,pwd) values(#{id},#{username},#{password})")
int insertUser(User user);
//删
@Delete("delete from user where id = #{id}")
int deleteUserById(@Param("id") int id);
//查
@Select("select * from user")
List<User> selectUser();
//改
@Update("update user set username = #{username},pwd = #{pwd} where id = #{id}")
int updateUser(User user);
}
- Mybatis-config.xml
<mappers>
<mapper class="com.bin.dao.UserMapper"/>
</mappers>
- 测试类
public class Test01 {
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser(new User(4,"小斌","123123"));
sqlSession.close();
}
}
// UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// List<User> users = mapper.selectUser();
// for (User user : users) {
// System.out.println(user);
// }
// mapper.deleteUserById(4);
// mapper.insertUser(new User(4,"小洪","1112223333"));
注意
- 使用注解进行CRUD时,只需要在接口的方法声明上添加相对应的注解。
- 当参数类型为基本数据类型或String时,需要添加@Param()注解
- 当参数类型为引用数据类型时,则不需要添加@Param()注解
- 注解只能满足简单的SQL语句,因此还是建议使用Mapper.xml文件。
一对多和多对一

根据视角不同:
- 对于老师而言,一个老师可以有多个学生,这就是一对多
- 对于学生而言,多个学生可以关联一个老师,这就是多对一
在日常生活也有类似例子:比如学生信息表会出现的,学院 --- 姓名
根据老师与学生关系可以设计两个小案例来说明在Mybatis中一对多和多对一应该如何处理。
一对多
对于该关系,表和实体类有不同做法:
- 表:利用外键
- 实体类:在老师的实体类中添加学生List属性
这样,就引出了要解决的核心问题:解决字段名与属性名不匹配的问题。
解决方案:利用resultMap
开始测试
-
搭建测试环境
- 在数据库中执行以下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=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);


-
创建核心配置文件和db.properties文件
-
根据表中字段编写相应的实体类
public class Teacher {
private int id;
private String name;
/*划重点这里学问挺深的,老师与学生关系是一对多的,因此在实体类中,学生实体类是正常创建的,
而老师实体类会因为该关系,在属性中添加学生列表*/
private List<Student> students;
public Teacher() {
}
public Teacher(int id, String name, List<Student> students) {
this.id = id;
this.name = name;
this.students = students;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
", students=" + students +
'}';
}
}
public class Student {
private int id;
private String name;
private int tid;
public Student() {
}
public Student(int id, String name, int tid) {
this.id = id;
this.name = name;
this.tid = tid;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getTid() {
return tid;
}
public void setTid(int tid) {
this.tid = tid;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
-
编写获得SqlSessionFactory的工具类
-
TeacherMapper
public interface TeacherMapper {
// Teacher getTeacher();
//根据输入的老师id查询出所有学生
List<Teacher> getTeacher(@Param("tid") int id);
}
- TeacherMapper.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.bin.dao.TeacherMapper">
<select id="getTeacher" resultMap="TeacherStudent">
select t.id as tid,t.name as tname,s.name as sname from student s,teacher t where s.tid = t.id
</select>
<resultMap id="TeacherStudent" type="teacher">
<result property="id" colum="tid" />
<result property="name" colum="tname"/>
<!--可以这样理解,List是一个集合,因此这里要使用集合标签-->
<collection property="students" ofType="student">
<result property="name" colum="sname"/>
</collection>
</resultMap>
</mapper>
- 将Mapper注册到核心配置文件中
运行结果

多对一
同样,对于该关系,表和实体类有不一样的做法:
-
表:利用外键
-
实体类:将学生与老师关联起来,或者说是将老师类封装到学生实体类中
同样,也会引出核心问题:字段名与实体类属性不匹配
解决方案:利用resultMap
开始测试
-
搭建测试环境与一对多一致
-
编写实体类
public class Teacher {
private int id;
private String name;
public Teacher(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
public Teacher() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Student {
private int id;
private String name;
//维持Teacher类的引用
private Teacher teacher;
public Student() {
}
public Student(int id, String name, Teacher teacher) {
this.id = id;
this.name = name;
this.teacher = teacher;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", teacher=" + teacher +
'}';
}
}
- StudentMapper
public interface StudentMapper {
//查询学生id,名字以及对应的老师
List<Student> getStudentList();
}
- StudentMapper.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.bin.dao.StudentMapper">
<select id="getStudentList" resultMap="StudentTeacher">
select s.id as sid,s.name as sname,t.name as tname from student s,teacher t where s.tid = t.id
</select>
<resultMap id="StudentTeacher" type="student">
<result property="name" colum="sname"/>
<result property="id" colum="sid"/>
<association property="teacher" type="teacher">
<result property="name" colum="tname"/>
</association>
</resultMap>
</mapper>
- 将Mapper注册到核心配置文件中
运行结果

注意
一对多和多对一,在实体类和在数据库表中分别是怎么进行处理的?
一对多:
- 表:外键
- 实体类:利用集合
多对一:
- 表:外键
- 实体类:利用关联关系
由于实体类与表的处理是不同的,因此就必定会存在数据库表字段名与实体类属性不一致的问题,为此就MyBatis就引入了解决方案。
了解这一点比知道解决方案更为重要。
动态SQL
回顾一下痛苦经历

要实现一个简单的连表查询,要用到字符串追加,然后还要设定参数。
现在,用官方的话来说就是:动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
元素种类:
-
if:若
-
choose (when, otherwise):用于条件中,类似于switch...case...
-
trim (where, set):用于update语句中
-
foreach:用于条件中
if
- BlogMapper
//通过If获得数据
List<Blog> getBlogsIf(Map map);
- BlogMapper.xml
<select id="getBlogsIf" 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>
<if test="views != null">
and views > #{views}
</if>
</select>
但是,在sql语句中出现1=1有些不规范
为此,更加合理的做法应该是:
<select id="getBlogsWhereIf" parameterType="map" resultType="blog">
select * from blog
<!--若第一个条件为null,where会智能地把and去掉-->
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
<if test="views != null">
and views > #{views}
</if>
</where>
</select>
choose
- BlogMapper
//通过Choose和when来获得数据
List<Blog> getBlogListByChooseWhen(Map map);
- BlogMapper.xml
<select id="getBlogListByChooseWhen" parameterType="map" resultType="Blog">
select * from blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
author = #{author}
</when>
<otherwise>
1 = 1
</otherwise>
</choose>
</where>
</select>
- 测试类
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
map.put("title","MyBatis");
map.put("author","狂神说");
List<Blog> blogs = mapper.getBlogListByChooseWhen(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
set
- BlogMapper
//通过where和set来获得数据
int updateBlogByWhereSet(Map map);
- BlogMapper.xml
<update id="updateBlogByWhereSet" parameterType="map">
update blog
<!--可以更新任意多的字段-->
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
<if test="views !=null">
views = #{views}
</if>
</set>
<where> id = #{id}</where>
</update>
- 测试类
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
map.put("id","908b088d5a6f4cdc89b59bbe6e81e4d3");
map.put("title","JavaSE");
map.put("author","小斌");
map.put("views",2000);
mapper.updateBlogByWhereSet(map);
sqlSession.commit();
sqlSession.close();
foreach
- BlogMapper
//通过foreach来获得数据
List<Blog> selectByForeach(Map map);
- BlogMapper.xml
<select id="selectByForeach" resultType="blog">
select * from blog
<where>
views in
<!--collection:集合名,open:以...开始,close:以...闭合,separator:分隔符,item:集合内元素-->
<foreach collection="viewsList" open="(" close=")" separator="," item="views">
#{views}
</foreach>
</where>
</select>
- 测试类
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
ArrayList<Integer> viewsList = new ArrayList<Integer>();
viewsList.add(9999);
viewsList.add(1000);
map.put("viewsList",viewsList);
List<Blog> blogs = mapper.selectByForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
缓存

什么是缓存?
有时候,某些数据是会经常需要访问的,像硬盘内部的缓存(暂存器的一种)会将读取比较频繁的一些数据存储在缓存中,再次读取时就可以直接从缓存中直接传输。
- 什么是缓存 [ Cache ]?
- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
- 为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率。
- 什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据。【可以使用缓存】
MyBatis中的缓存
MyBatis中的缓存可以分为两类:一级缓存和二级缓存。
一级缓存
作用域:一个SqlSession会话期间,也就是SqlSession的存活周期。
测试
- UserMapper
//根据id查询用户
User getUserById(@Param("id") int id);
//更新用户
int updateUser(Map map);
- UserMapper.xml
<select id="getUserById" resultType="user">
select * from user where id = #{id}
</select>
<update id="updateUser" parameterType="map">
update user
<set>
<if test="username != null">
username = #{username},
</if>
<if test="pwd != null">
pwd = #{pwd}
</if>
</set>
where id = #{id}
</update>
- 测试类
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.getUserById(1);
System.out.println(user1);
Map map = new HashMap();
map.put("id",2);
map.put("username","djalkdja0");
map.put("pwd","dadjlka");
//测试在两个查询之间插入一条更新语句
mapper.updateUser(map);
//清空缓存
sqlSession.clearCache();
System.out.println("===============================");
User user2 = mapper.getUserById(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession.close();
- 结果
正常情形下,对同一个id的用户数据进行查询,只执行了一条sql语句,也就代表了一级缓存存在的机制;当在两条查询之间插入一条更新语句或者清空缓存的语句时,执行了两条相同的sql语句。
二级缓存
由于一级缓存的作用域实在太小,为此引入了二级缓存。
作用域:基于namespace级别的缓存,一个Mapper文件对应一个缓存区。
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
测试
- 配置核心配置文件
<!--显式配置缓存-->
<setting name="cacheEnabled" value="true"/>
- 配置UserMapper.xml文件
<cache />
-
实体类User实现可序列化接口(这一步很重要,别忘了)
-
测试类
@Test
public void test02(){
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = mapper1.getUserById(1);
System.out.println(user1);
//会话结束后,会将一级缓存的内容存到二级缓存中去
sqlSession1.close();
System.out.println("===============================");
User user2 = mapper2.getUserById(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession2.close();
}
MyBatis中的缓存机制
在开启二级缓存后

动态SQL的练习题
根据接口中的方法编写对应的SQL语句:

- BillMapper.xml
<select id="getBillCountByProviderId" >
select count(bill) from bill where providerId = #{providerId}
</select>
<insert id="add" parameterType="bill">
insert into bill (billCode,productName,productDesc,productUnit,productCount,totalPrice,isPayment,createBy,creationDate,modifyBy,modifyDate,providerId)
values(#{billCode},#{productName},#{productDesc},#{productUnit},#{productCount},#{totalPrice},#{isPayment},#{createBy},#{creationDate},#{modifyBy},#{modifyDate},#{providerId})
where id = #{id}
</insert>
<select id="getBillList" resultType="Bill">
select * from bill
<where>
<if test="productName != null">
productName = #{productName}
</if>
<if test="providerId != null">
and providerId = #{providerId}
</if>
<if test="isPayment ! = null">
and isPayment = #{isPayment}
</if>
</where>
limit #{from},#{pageSize}
</select>
<select id="getBillCount">
select count(bill) from bill
<where>
<if test="productName != null">
productName = #{productName}
</if>
<if test="providerId != null">
and providerId = #{providerId}
</if>
<if test="isPayment != null">
and isPayment = #{isPayment}
</if>
</where>
</select>
<delete id="deleteById">
delete from bill where id = #{id}
</delete>
<select id="getBillById" resultType="Bill">
select * from user where id = #{id}
</select>
<update id="modify" parameterType="Bill">
update user
<set>
<if test="billCode != null">
billCode = #{billCode}
</if>
<if test="productName != null">
productName = #{productName}
</if>
<if test="productDesc != null">
productDesc = #{productDesc}
</if>
<if test="productUnit != null">
productUnit = #{productUnit}
</if>
<if test="produntCount != null">
productCount = #{productCount}
</if>
<if test="totalPrice != null">
totalPrice = #{totalPrice}
</if>
<if test="isPayment != null">
isPayment = #{isPayment}
</if>
<if test="createdBy != null">
createdBy = #{createdBy}
</if>
<if test="creationDate != null">
creationDate = #{creationDate}
</if>
<if test="modifyBy != null">
modifyBy = #{modifyBy}
</if>
<if test="modifyDate">
modifyDate = #{modifyDate}
</if>
<if test="providerId != null">
providerId = #{providerId}
</if>
</set>
where id = #{id}
</update>
<delete id="deleteBillByProviderId">
delete from bill where providerId = #{providerId}
</delete>




浙公网安备 33010602011771号