文章目录
第一个MyBatis程序
项目目录结构
创建一个空Maven项目
1. 导入依赖
依赖在pom.xml
导入
<!--导入依赖-->
<dependencies>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
</dependencies>
2. 编写核心配置文件
在src/main/resource
中创建mybatis-config.xml
文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
与MySQL5.x不同的是,MySQL8.x的驱动是
com.mysql.cj.jdbc.Driver
url中需要设置是否开启SSL连接:
useSSL=true
/设置编码:useUnicode=true&characterEncoding=utf8
并且,MySQL8.x必须设置时区
serverTimezone=Asia/Shanghai
在这里
&
需要转义成&
3. 编写工具类连接数据库
public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory = null;
//获取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();
}
}
SqlSession中包含了面向数据库执行的SQL命令的所有方法
4. pojo对象
在pero.fisher.pojo
中创建一个与表相关的类
public class User {
private int id;
private String name;
private String passwd;
public User() {
}
public User(int id, String name, String passwd) {
this.id = id;
this.name = name;
this.passwd = passwd;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getPasswd() {
return passwd;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
与User类对应的表结构
5. 编写Mapper接口
在pero.fisher.dao
中
创建UserMapper
接口
public interface UserMapper {
List<User> getUserList();
}
创建UserMapper.xml
用来实现UserMapper接口
<?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">
<!--namespace绑定一个mapper接口-->
<mapper namespace="pero.fisher.dao.UserDao">
<!--查询语句-->
<!--id为接口的方法名-->
<!--resultType: select返回值类型-->
<select id="getUserList" resultType="pero.fisher.pojo.User">
select * from mybatis.user
</select>
</mapper>
6. 测试
在test.java.pero.fisher.dao
中编写测试类UserDaoTest
public class UserDaoTest {
@Test
public void test(){
//获取SqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//执行SQL
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> userList = userDao.getUserList();
for (User user : userList) {
System.out.println(user);
}
//关闭SqlSession
sqlSession.close();
}
}
如果你遇到下面这个错误
org.apache.ibatis.binding.BindingException: Type interface pero.fisher.dao.UserDao is not known to the MapperRegistry.
这个错误是绑定异常
每个Mapper.xml文件都需要在MyBatis核心配置文件注册
解决办法
在核心配置文件mybatis-config.xml
中加入
<mappers>
<mapper resource="pero/fisher/dao/UserMapper.xml" />
</mappers>
如果你遇到下面这个错误
java.lang.ExceptionInInitializerError
### The error may exist in pero/fisher/dao/UserMapper.xml
这是一个文件过滤问题
原因是Maven默认的配置文件是存放在resource中,当前
Mapper.xml
是放在java
中,此时Maven无法找到这个文件
解决办法
在工程配置文件pom.xml
中添加
<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>
这样可以打开java下的文件过滤
增删改操作
增删改操作需要提交事务
添加数据
UserMapper.java
中
//添加一行数据
int addUser(User user);
UserMapper.xml
中
<!--对象中的属性可以直接取出来-->
<insert id="addUser" parameterType="pero.fisher.pojo.User" >
insert into mybatis.user (id, name, passwd) values(#{id}, #{name}, #{passwd})
</insert>
id: 方法名
parameterType: 参数类型,如果是对象时,需要写权限定名
参数如果是对象,对象中的属性可以直接取出来
UerMapperTest.java
中
@Test
public void assUserTest(){
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper userDao = sqlSession.getMapper(UserMapper.class);
int ret = userDao.addUser(new User(4, "找流", "678"));
System.out.println(ret);
//提交事务
sqlSession.commit();
sqlSession.close();
}
增删改操作需要
提交事务
SqlSession使用完需要
.close()
关闭
删除数据
UserMapper.java
中
//删除一行数据
int delUser(int id);
UserMapper.xml
中
<delete id="delUser" parameterType="int">
delete from mybatis.user where id = #{id}
</delete>
修改数据
UserMapper.java
中
//修改数据
int update(User user);
UserMapper.xml
中
<update id="update" parameterType="pero.fisher.pojo.User">
update mybatis.user set name = #{name}, passwd = #{passwd} where id = #{id}
</update>
利用Map传参
利用Map传参可以让传递参数变得更加灵活,也可以应用Map传递多个参数
UserMapper.java
中
int update2(Map<String, Object> map);
Map的类型为<String, Object>
UserMapper.xml
中
<update id="update2" parameterType="map">
update mybatis.user set name = #{username} where id = #{id}
</update>
Map传参不会像传递pojo类一样严格要求#{}中的参数名与类中相同.
也不必在调用方法时构建一个类,特别是当需要的参数很少,而类中的参数很多,构建类的过程会显得十分臃肿和麻烦.
UserMapperTest.java
中
@Test
public void update2Test() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("username", "flash");
map.put("id", 1);
int ret = mapper.update2(map);
System.out.println(ret);
sqlSession.commit();
sqlSession.close();
}
在调用方法时使用put()为Map传值
环境配置
MyBatis默认事务管理器是JDBC,默认连接池(POOLED)
使用配置文件配置环境
在resources目录中创建db.properties
文件
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306?\
useSSL=false&useUnicode=TRUE&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username=root
password=root
在mybatis-config.xml
中引入配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<!--引入外部配置文件-->
<properties resource="db.properties" />
<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>
</environments>
<mappers>
<mapper resource="pero/fisher/dao/UserMapper.xml" />
</mappers>
</configuration>
类型别名
为Java类型设置一个短的名字,用来减少完全限定名的冗余
在mybatis-congif.xml
中
可以自定义类型别名
<typeAliases>
<typeAlias type="pero.fisher.pojo.User" alias="User"/>
</typeAliases>
也可以指定包名,MyBatis会使用 Bean 的首字母小写的非限定类名来作为它的别名
<typeAliases>
<package name="pero.fisher.pojo"/>
</typeAliases>
指定包名也可以自定义别名,当类有@Alias("name")
时,会使用注解值作为别名
@Alias("author")
public class Author {
...
}
映射器
告诉 MyBatis 到哪里去找到SQL语句
1. 使用相对于类路径的资源引用
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
这是最常用的方法
2. 使用映射器接口实现类的完全限定类名
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
这种方法有两个要求
- 接口与它的SQL文件必须同名
- 接口与它的SQL文件必须在同一个包下
3. 将包内的映射器接口实现全部注册为映射器
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
这种方法的要求与第二种方法相同
ResultMap结果集映射
运用ResultType访问数据库,数据库字段名必须与ResultType类中的属性名一一对应
当属性名与字段名不同时可以通过结果集映射,映射字段名对应的方法名
<resultMap id="UserMap" type="user">
<result column="id" property="id"/>
<result column="passwd" property="pass"/>
</resultMap>
<select id="getUserById" parameterType="int" resultMap="UserMap">
select * from mybatis.user where id = #{id}
</select>
将resultType修改成resultMap,然后定义resultMap
在resultMap中只需要映射不同的部分,属性名与字段名相同的可以自动映射
日志
STDOUT_LOGGING
STDOUT_LOGGING
为默认日志工厂
在mybatis-config.xml
中添加
<settings>
<!--默认日志工厂-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
设置日志工厂之后,在调用Mapper方法时会显示出log
Log4j
- log4j能够控制输出的目的地
- 能够控制输出格式
- 可以定义每条日志信息的级别
- 通过一个配置文件配置,不需要编写代码
log4j的使用
pom.xml
中导入log4j包
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
resources
中编写log4j配置文件log4j.properties
### set log levels ###
# 日志级别为DEBUG输出在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=[%d{yy/MM/dd HH:mm:ss:SSS}]-%l:%m%n
### 输出到日志文件 ###
log4j.appender.File=org.apache.log4j.RollingFileAppender
log4j.appender.File.File=./log/app.log
log4j.appender.File.MaxFileSize=10MB
log4j.appender.File.Threshold=ALL
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%c]%m%n
# 日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.mysql=DEBUG
log4j.logger.java.mysql.Statement=DEBUG
log4j.logger.java.mysql.ResultSet=DEBUG
log4j.logger.java.mysql.PreparedStatement=DEBUG
- 设置,在
mybatis-conifg.xml
中
<settings>
<setting name="logImpl" value="log4j"/>
</settings>
- log4j使用
@Test
public void mytest() {
log.info("普通信息");
log.debug("debug信息");
log.error("错误信息");
}
分页
SQL语句中使用limit分页
select * from user limit startIndex, pageSize;
MyBatis实现分页
UserMapper.java
中
//分页查询
List<User> getUserByLimit(Map<String, Object> map);
UserMapper.xml
中
<resultMap id="UserMap" type="user">
<result column="passwd" property="pass"/>
</resultMap>
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex}, #{pageSize}
</select>
由于Bean中的属性名与数据库字段名不一致,因此这里应用了
resultMap
进行结果集映射
UserMapperTest.java
中
@Test
public void getUserByLimitTest() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("startIndex", 0);
map.put("pageSize", 2);
List<User> userByLimit = mapper.getUserByLimit(map);
for (User user : userByLimit) {
System.out.println(user);
}
sqlSession.close();
}
自动提交事务
SqlSessionFactory.openSession()
方法中存在一个boolean类型参数boolean autoCommit
,默认为false,当传递参数为true时,创建的SqlSession可以自动提交事务.
注解的应用
增删改查(CRUD)
在Mapper中可以通过注解@Select("")
/@Insert("")
/@Update("")
/@Delete("")
直接写实现,而不需要单独写一个Mapper.xml
//根据id查询数据
@Select("select * from mybatis.user where id = #{uid}")
User getUserById(@Param("uid") int id);
@Param注解
@Param注解可以标注某个参数在sql里面的名字.
在Mapper接口需要传递多个参数时,必须要使用@Param注解
传递一个基本类型的参数时也建议使用@Param注解
上面的实例代码也使用了@Param注解
@Select("select * from mybatis.user where id = #{uid}")
User getUserById(@Param("uid") int id);
多对一处理方法
假如有一个学生和老师两张表,学生与老师是多对一的关系,即学生中有一个外键指向老师,表结构如下:
需求是要查询出学生的信息,包括学生对应老师的信息
重点是pojo类中需要得到一个类
方式一: 按照查询嵌套处理
在Mapper中写一个接口
//查询所有学生及对应老师的信息
List<Student> getStudent();
Mapper.xml的实现
<!--
步骤:
1.查询所有老师
2.根据学生的tid查询老师
-->
<select id="getStudent" resultMap="StudentTeacher">
select * from mybatis.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 mybatis.teacher where id = #{id}
</select>
这是一种
子查询
的方法当查询学生时,只能查询到tid,而不是tid对应的Teacher类,因此需要通过
resultMap
将数据库中的tid字段转换成Mapper中的Teacher类
方法二: 按照结果嵌套处理
<select id="getStudent" resultMap="StudentTeacher">
select s.id sid, s.name sname, t.name tname
from mybatis.teacher t, mybatis.student s
where s.tid = t.id
</select>
<resultMap id="StudentTeacher" type="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
这是一种连表查询的方法
直接通过sql语句查询所有信息
由于Student有一个参数是Teacher,查询出来的数据需要通过resultMap指定
一对多的处理方法
一对多的需求是在pojo类中需要得到一个集合而并非一个类
一对多与多对一的方法差类似
<select id="getTeacher" resultMap="TeacherStudent">
select t.id tid, t.name tname, s.name sname
from mybatis.teacher t, mybatis.student s
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-->
<collection property="students" ofType="student">
<result property="name" column="sname"/>
</collection>
</resultMap>
有几处不同的是
- 集合需要用
<collection>
标签而不是- 集合的类型要用
ofType
而不是javaType
动态SQL
根据不同的条件生成不同的SQL语句
if语句
<select id="getBlogsIf" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
<where>
标签可以智能判断如果语句中不需要and会自动将and去掉
choose(when, otherwise)语句
相当于switch
语句
<select id="getBlogsChoose" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<when test="views != null">
and views = #{views}
</when>
<otherwise>
and views = 1000
</otherwise>
</choose>
</where>
</select>
标签相当于switch语句, 当一个条件满足时,choose会终止, 不会执行下一个条件
set
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id}
</update>
标签与标签有相似的功能,就是智能去除SQL拼接时多余的
,
sql代码片段
用sql代码片段实现代码服用
<sql id="if-title-author">
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</sql>
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<include refid="if-title-author"></include>
</set>
where id = #{id}
</update>
通过写sql片段,通过引用sql片段
foreach语句
例如select * from mybatis.blog where (id = 1 or id = 2 or id = 3)
这样的语句, 需要获取一串元素, 就可以用<foreach>
实现
<select id="getBlogForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="(" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
collection: 遍历的
item: 遍历出来的元素
open: 开始字符
close: 结束字符
separator: 分割字符
缓存
查询数据时,频繁连接数据库十分耗资源,
因此引入缓存机制,将查询的结果放入缓存中,当再次查询相同数据时,直接查缓存
- 缓存解决了高并发系统的性能问题
- 缓存的作用: 减少与数据库的交互次数, 减少系统开销, 提高系统效率
- 使用缓存的场景: 经常查询且不经常改变的数据
一级缓存
一级缓存也叫本地缓存
-
MyBatis中默认开启一级缓存
-
一级缓存只在一次SqlSession中有效,即在创建SqlSession到关闭SqlSession之间有效
一级缓存失效的情况
- 增删改操作可能会改变原来的数据,因此必定刷新缓存
- 查询不同的数据,上一个缓存没有用
- 查询不同的Mapper.xml
- 手动清理缓存, 缓存就不存在了
二级缓存
二级缓存也叫全局缓存
- 二级缓存是基于namespace级别的缓存, 作用域是一个命名空间
- 工作机制
- 一个会话查询一条数据,这个数据会放在当前会话的一级缓存
- 当当前会话关闭时,一级缓存的数据会保存到二级缓存中,一级缓存的数据会被清空
- 新会话查询数据时会从二级缓存获取
- 不同Mapper查询的数据会放在自己对应的缓存中
步骤
-
开启全局缓存, 在
mybatis-config.xml
配置文件中<!--开启全局缓存--> <setting name="cacheEnabled" value="true"/>
-
在要使用二级缓存的Mapper中开启缓存
<cache/>
也可以自定义参数
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
二级缓存的异常处理:
-
在直接使用
<cache/>
时,会出现异常Cause: java.io.NotSerializableException: pero.fisher.pojo.Blog
-
这个错误是由二级缓存使用时,是从一级缓存中获取,实体类需要进行序列化
序列化的方法:
- 实体类实现序列化接口
Serializable
即可 - 实体类尽量都进行序列化,以免出现序列化引发的异常