框架学习—MyBatis(自我学习用)
一、概述
链接: link.
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
二、第一个MyBatis程序
步骤:
1、搭建环境
①、打开IDEA,创建一个maven项目。
②、打开设置,看maven是不是IDEA自带的,是的话改成自己电脑本地的版本
③、删除当前工程目录下的src(我们要把当前工程作为父工程,好处是可以对公共部分的包统一管理、导入;具体工程可以新建一个module)
④、导包:需要用的包有mysql驱动、mybatis、Junit、lombok等
2、创建工程
①、新建一个module,作为具体工程
②、编写核心配置文件mybatis-config.xml
③、编写mybatis工具类(把通过SqlSessionFactory获取SqlSession封装成一个工具类)
④、编写代码:实体类、Dao/mapper接口、接口实现类(这里就是一个Mapper配置文件,由原来的编写UserDaoImpl转换为编写一个UserMapper.xml文件)
⑤、进行测试
具体代码如下:
核心配置文件mybatis-config.xml,放在src/main/resources目录下
<?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核心配置文件-->
<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="root"/>
</dataSource>
</environment>
</environments>
//每一个mapper文件都要在核心配置文件中注册
<mappers>
<mapper resource="com/wl/dao/UserMapper.xml"/>
</mappers>
</configuration>
mybatis工具类,可以方便的获取SqlSession对象
通过SqlSessionFactoryBuilder获取SqlSessionFactory ,再通过SqlSessionFactory 获取SqlSession
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static{
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
//提供通过sqlSessionFactory获得SqlSession的方法
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
实体类pojo
这里直接使用lombok生成有参构造,无参构造,getter、setter、toString等方法
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
private int id;
private String name;
private String pwd;
}
Dao/Mapper接口
public interface UserDao {
List<User> getUserList();
}
接口实现类/Mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--注释:namespace绑定一个对应的Dao/Mapper接口 -->
<mapper namespace="com.wl.dao.UserDao">
<!--注释:id绑定对应接口的方法名 resultType设置对应方法的返回值类型 -->
<select id="getUserList" resultType="com.wl.pojo.User">
select * from mybatis.user;
</select>
</mapper>
<!--注释: 注意,Mapper写完了要到核心配置文件中注册才能生效 -->
测试
public class UserDaoTest {
@Test
public void test(){
//1、获取 SqlSession对象
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//这里相当于通过sqlSession.getMapper方法获取一个UserDao的实现类对象
UserDao mapper = sqlSession.getMapper(UserDao.class);
//通过mapper对象就可以执行类中定义的方法
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
//最要关闭sqlSession
sqlSession.close();
}
}
需要注意的问题:
①、由于maven约定大于配置,因此有可能会导出不了Mapper.xml文件,解决办法是在父工程的pom.xml文件中加入以下代码即可。
<!--由于maven约定大于配置,所以要在build中配置resources,来防止资源导出失败的问题-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
②、每个Mapper.xml文件都要在核心配置文件mybatis-config.xml中注册才能生效
三、CRUD
现在我们的主要操作都是在mapper/dao(例如UserMapper)接口和mapper.xml(例如UserMapper.xml)文件中进行,也就是说除了这两部分,其他部分的代码都不用进行修改。
首先在mapper/dao接口中写CRUD方法,然后在mapper.xml进行相应的配置。代码如下
public interface UserMapper {
//查询全部用户
List<User> getUserList();
//根据id查询用户
User getUserById(int id);
//添加用户
int addUser(User user);
//修改用户
int updateUser(User user);
//根据id删除用户
int deleteUser(int id);
}
<?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绑定一个对应的Dao/Mapper接口 -->
<mapper namespace="com.wl.dao.UserDao">
<!--id绑定对应接口的方法名 resultType设置对应方法的返回值类型 -->
<select id="getUserList" resultType="com.wl.pojo.User">
select * from mybatis.user;
</select>
<!--查-->
<select id="getUserById" parameterType="int" resultType="com.wl.pojo.User">
select * from mybatis.user where id = #{id}
</select>
<!--增-->
<insert id="addUser" parameterType="com.wl.pojo.User">
insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd})
</insert>
<!--改-->
<update id="updateUser" parameterType="com.wl.pojo.User">
update mybatis.user set name = #{name} ,pwd=#{pwd} where id=#{id}
</update>
<!--删-->
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id}
</delete>
</mapper>
参数说明:
namespace:绑定一个对应的Dao/Mapper接口
id:接口中的函数名
parameterType:方法传入参数类型
resultType:方法返回值类型
测试代码:
由于操作大都一样,这里只放了增的操作,需要注意的是增删改操作要提交事务才能成功(sqlSession.commit())
@Test
public void addUser(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
mapper.addUser(new User(4,"牵牛花","123456"));
//注意增删改操作要提交事务才能成功
sqlSession.commit();
sqlSession.close();
}
注意事项:增删改操作要提交事务才能成功,即要执行sqlSession.commit()才行
使用map来传参
当实体类或者数据库中的表,字段或者参数过多,可以考虑使用map来传参。因为map传参更加灵活,使用map我们可以只传递我们需要修改的值,而不用像传入实体类(比如User)一样,需要传递所有字段
代码如下:
1、写接口函数
//通过map添加一个用户
int addUser2(Map<String,Object> map);
2、mapper里添加配置
<!-- 这里values 里的参数名就要和map中保持一致-->
<insert id="addUser2" parameterType="map">
insert into mybatis.user (id, name, pwd) values (#{userid},#{username},#{userpwd})
</insert>
3、进行测试:
@Test
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("username","油菜花");
map.put("userpwd","000101");
mapper.addUser2(map);
sqlSession.commit();
sqlSession.close();
}
四、配置解析
1、核心配置文件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核心配置文件-->
<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="com/wl/dao/UserMapper.xml"/>
</mappers>
</configuration>
参数解释:
environments(环境配置) :MyBatis 可以配置成适应多种环境。尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境
transactionManager(事务管理器):默认设置为JDBC
dataSource(数据源):默认设置为POOLED
properties(属性):可以通过properties来引入外部配置文件(例如数据库配置文件db.properties)
db.properties也要放在src/main/resources目录下,内容如下
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=root
然后在核心配置文件中引入该配置文件
<!--引入外部配置文件-->
可以直接引入
<properties resource="db.properties" />
也可以在引入文件的同时,在properties 内部增加一些属性配置
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
但需要注意的是,如果内部和外部属性配置有相同字段,则优先使用外部的
类型别名(typeAliases):类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
可以使用typeAlias给某个类起别名
<typeAliases>
<typeAlias type="com.wl.pojo.User" alias="User"/>
</typeAliases>
也可以使用package指定一个包名,MyBatis会在包名下面搜索需要的Java Bean(实体类),默认使用类名首字母小写作为别名
<typeAliases>
<package name="com.wl.pojo"/>
</typeAliases>
还可以通过注解给类起别名,并且通过注解设置的别名将优先使用
@Alias("user")
public class User {
private int id;
private String name;
private String pwd;
}
设置别名后,在mapper文件里的参数类型就可以不使用类的全限定名,直接使用类的别名即可
<select id="getUserList" resultType="user">
select * from mybatis.user;
</select>
mappers(映射器):每个mapper配置文件都需要到核心配置文件中进行注册,才能生效
方式一:通过resource
<mappers>
<mapper resource="com/wl/dao/UserMapper.xml"/>
</mappers>
方式二:通过class
<mappers>
<mapper class="com.wl.dao.UserMapper"/>
</mappers>
方式三:通过package
<mappers>
<package name="com.wl.dao"/>
</mappers>
需要注意的是:通过class和package 注册的话,需要保证接口和它的mapper配置文件必须同名且在同一个包下
五、ResultMap结果集映射
ResultMap主要作用就是解决实体类属性名与数据库字段名不一致的问题
解决实体类属性名与数据库字段名不一致有两种办法,一是起别名,二就是使用ResultMap
1、起别名
比如实体类的属性名password和数据库字段名pwd不一致
<select id="getUserById" parameterType="int" resultSet="user">
select id,name,pwd password from mybatis.user where id = #{id}
</select>
2、通过ResultMap结果集映射
<!--结果集映射: column表示字段名 property表示实体类属性名-->
<!--id代表该结果集映射 type表示实际指向的类型-->
<resultMap id="UserMap" type="user">
<!--<result column="id" property="id"/>-->
<!--<result column="name" property="name"/>-->
<result column="pwd" property="password"/>
</resultMap>
<select id="getUserById" parameterType="int" resultMap="UserMap">
select * from mybatis.user where id = #{id}
</select>
六、日志
日志的好处是,不止会输出结果,还会输出其他的一些数据库相关信息
如图:
1、在mybatis核心配置文件中配置日志
<!--在核心配置文件中配置日志-->
<settings>
<!--标准的日志工厂实现 STDOUT_LOGGING-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
七、通过map传参,实现Limit分页查询
1、接口:
//通过map传参,实现分页查询
List<User> getUserByLimit(Map<String,Integer> map);
2、mapper.xml配置
<select id="getUserByLimit" resultMap="UserMap">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
3、测试:
@Test
public void getUserByLimit(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex",0);
map.put("pageSize",2);
List<User> userList = mapper.getUserByLimit(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
八、使用注解开发
除了mapper.xml配置文件完成语句的映射,还可以使用 Java 注解来配置完成语句的映射。使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句(比如实体类属性名和数据库字段名不匹配的情况),Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。
代码如下:
1、接口:
public interface UserMapper {
//这里通过注解完成语句的映射
@Select("select * from mybatis.user")
List<User> getUsers();
}
2、在核心配置文件中绑定接口
<mappers>
<mapper class="com.wl.dao.UserMapper"/>
</mappers>
3、测试
@Test
public void getUsers(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
本质上是通过反射机制实现,底层是动态代理。
注解实现CRUD:
//当有函数有多个参数时,则每个参数前要加上@Param(),并且sql语句中的字段名名要与@Param()里的参数名保持一致。
@Select("select * from user where id = #{id}")
User getUserById(@Param("id") int id);
// 引用类型的参数前则不用加@Param()
@Insert("insert into user (id,name,pwd) values(#{id},#{name},#{password})")
int addUser(User user);
@Update("update user set name=#{name},pwd=#{password} where id=#{id}")
int updateUser(User user);
@Delete("delete from user where id=#{id}")
int deleeUserById(@Param("id") int id);
关于@Param():
①、接口方法中基本类型参数或者String类型的参数,需要加上
②、引用类型参数则不用加
③、当方法中仅有一个为基本类型的参数的话,可以忽略不加
④、sql语句中引用的就是@Param()中设定的属性名
九、association和collection
1、association:多对一
两种方式:子查询、联表查询
学生实体类接口
@Data
public class Student {
private int id;
private String name;
//多个学生关联一个老师
private Teacher teacher;
}
老师实体类接口
@Data
public class Teacher {
private int id;
private String name;
}
<!--
多对一(association)查询的两种方式:子查询、联表查询
-->
<!--方式一:子查询-->
<select id="getStudents" resultMap="StudentMap">
select * from student
</select>
<resultMap id="StudentMap" type="Student">
<result property="id" column="id" />
<result property="name" column="name"/>
<!--javaType表示属性类型,select表示要使用的子查询-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id=#{tid}
</select>
<!--方式二:联表查询-->
<select id="getStudents2" resultMap="StudentMap2">
select s.id sid,s.name sname,t.name tname
from student s,teacher t
where s.tid=t.id
</select>
<resultMap id="StudentMap2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" column="tid" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
2 、collection:一对多
@Data
public class Teacher {
private int id;
private String name;
//一个老师对应多个学生
List<Student> students;
}
@Data
public class Student {
private int id;
private String name;
private int tid;
}
<!--
一对多查询(collection)的两种方式:
按照结果嵌套处理
按照查询嵌套处理
-->
<!--方式一:按照结果嵌套处理-->
<select id="getTeacherById" resultMap="TeacherMap">
select s.id sid, s.name sname, t.id tid, t.name tname
from student s, teacher t
where s.tid = t.id and tid = #{tid}
</select>
<resultMap id="TeacherMap" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--ofType表示集合中的泛型类型-->
<collection property="students" ofType="Student">
<result property="id" column="tid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
<!--方式二:按照查询嵌套处理-->
<select id="getTeacherById2" resultMap="TeacherMap2">
select * from teacher where id=#{tid}
</select>
<resultMap id="TeacherMap2" type="Teacher">
<collection property="students" javaType="ArrayList" ofType="Student" select="getStudent" column="id"/>
</resultMap>
<select id="getStudent" resultType="Student">
select * from mybatis.student where tid = #{id}
</select>
javaType:用来指定实体类中属性的类型
ofType: 用来指定集合中泛型的类型
十、动态SQL
动态 SQL 是 MyBatis 的强大特性之一,
常用的标签有:
if、choose (when, otherwise)、trim (where, set)、foreach
代码测试如下:
if标签
1、接口
//使用if查询
List<Blog> queryBlogIF(Map map);
2、mapper配置
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<if test="author != null">
author = #{author}
</if>
<if test="title != null">
and title = #{title}
</if>
</where>
</select>
3、测试
@Test
public void queryBlogIF(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
//map.put("title","蜡笔小新2");
map.put("author","狂神说");
List<Blog> blogs = mapper.queryBlogIF(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
choose标签:
//通过choose查询,类似于java中的switch case
List<Blog> queryBlogChoose(Map map);
<select id="queryBlogChoose" 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>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
set、where标签:
//更新用户信息
int updateBlog(Map map);
<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语句
十一、Mybatis缓存
链接: link.
缓存的目的就是提升查询的效率和减少数据库的压力,MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。MyBatis 默认开启一级缓存。
1、一级缓存(本地缓存):
一级缓存也叫本地缓存,MyBatis 的一级缓存是在会话(SqlSession)层面进行缓存的。MyBatis 的一级缓存是默认开启的,不需要任何的配置。
在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。
为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。
一级缓存的生命周期
①、MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
②、如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;
③、如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用;
④、SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用;
2、二级缓存
二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是namespace 级别的,可以被多个SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。如果你的MyBatis使用了二级缓存,并且你的Mapper和select语句也配置使用了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:二级缓存 —> 一级缓存 —> 数据库。
开启二级缓存的方法:
①、在核心配置文件添加设置
<setting name="cacheEnabled" value="true"/>
②、在mapper.xml文件中配置开启二级缓存标签
可以不进行配置
<cache/>
也可以进行一些自定义配置
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>