1. Mybatis
2. 通过SqlSessionFactory获取SqlSession
通过SqlSessionFactory获得Sqlsession,SqlSession中包含了面向数据库执行SQL命令所需的所有方法
- 获取SqlSession的工具类
//SqlSessionFactory 生产 sqlsession
//工具类,全部都是静态的
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
String resource = "mybatis-config.xml";
try {
//通过流读取资源
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSession(){
//通过sqlSessionFactory.openSession() 获得sqlsession,参数为true时就不需要手动commit
SqlSession sqlSession=sqlSessionFactory.openSession(true);
return sqlSession;
}
}
- mybatis-config.xml,包含了一些连接池、连接数据库的信息
- 注意! 需要在这里面注册Mapper关联的接口
<?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-->
<properties resource="db.properties"/>
<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=false&useUnicode=true&characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
<!--============================================================-->
<!--用properties配置数据库信息,environment id 可以配置多个连接方式-->
<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>
<!-- 注意! 需要在这里面注册Mapper关联的接口-->
<mappers>
<mapper resource="com/ning/dao/UserMapper.xml"></mapper>
</mappers>
</configuration>
3. 创建Mapper
-
原本用jdbc的方式写是这样的:
创建接口、创建接口的实现类、在实现类的方法里写sql语句 -
用mybatis的方式:
创建接口、抛弃了实现类,换用xml配置(mapper)的方式
- 创建一个mapper(在这之前我写好了一个叫UserMapper的接口,和一个User的实体类)
- 在jdbc中,查询完语句会有返回值,在mapper中用resultType或者resultMap来设定方法返回值类型
<?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 = 绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.ning.dao.UserMapper">
<!--绑定了接口,就相当于实现了接口,也需要实现接口中的方法-->
<!--id = 绑定接口中的方法 , resultType = 方法的返回值类型-->
<select id="getUserAll" resultType="com.ning.pojo.User">
select * from user
</select>
</mapper>
- 运行测试(在每次用完sqlsession的时候都需要关闭它,可以把这个关闭操作放到finally块中)
public class UserDaoTest {
@Test
public void test(){
SqlSession sqlSession =MybatisUtils.getSession();
try{
// 获取SqlSession,直接调用写好的MybatisUtils工具类
// 通过动态代理和反射机制返回了一个Mapper的实现类(这时候Mapper.xml就转换成实现类了)
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//直接调用接口中的方法,会对应mapper中绑定好 (select的id)执行里面的sql语句并返回结果
List<User> list = userMapper.getUserAll();
for (User user : list) {
System.out.println(user.getId());
}
}catch (Exception e){
}finally {
sqlSession.close();
}
}
}
3.1 ResultMap(映射结果集)
如果sql语句中查询的字段起了别名,那么在column
中也需要写入同样的别名
<mapper namespace="com.ning.dao.UserMapper">
<resultMap id="UserMap" type="User">
<!-- id为主键 -->
<id column="id" property="id"/>
<!-- column是数据库表的列名 , property是对应实体类的属性名 -->
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>
<!--返回值类型为上面起好别名封装好的resultMap id-->
<select id="selectUserById" resultMap="UserMap">
select id , name , pwd from user where id = #{id}
</select>
</mapper>
4. CRUD
- select
- id :对应的namespace中的方法名;
- resultType: Sql语句执行完的返回值;
- parameterType: 传入的参数类型;
<select id="getUserById" resultType="com.ning.pojo.User" parameterType="int">
select * from user where id = #{id}
</select>
- insert (增删改需要提交事务,在执行sql语句之后需要sqlSession.commit() )
<insert id="addUser" parameterType="com.ning.pojo.User" >
insert into mybatis.user(id,name,password) values (#{id},#{name},#{password})
</insert>
- update
<update id="updateUser" parameterType="com.ning.pojo.User" >
update user set name = #{name},password=#{password} where id=#{id}
</update>
- delete
<delete id="deleteUser" parameterType="int">
delete from user where id=#{id}
</delete>
- 用map集合传递参数(参数过多且项目体量小适合使用)
- parameterType=“map”
- sql语句中的参数为map的key
<delete id="deleteUserByMap" parameterType="map">
delete from user where id= #{helloid}
</delete>
@Test
public void deleteUserByMap(){
Map<String,Object> map =new HashMap<String, Object>();
map.put("helloid",9);
int num = userMapper.deleteUserByMap(map);
sqlSession.commit();
if (num>0){
System.out.println("修改成功");
}
sqlSession.close();
}
- Limit分页
<select id="getUserByLimit" resultMap="UserMap" parameterType="map">
select * from `user` limit #{startIndex},#{pageSize}
</select>
public void getUserByLimit(){
Map<String,Object> map = new HashMap<String, Object>();
map.put("startIndex",0);
map.put("pageSize",2);
List<User> list = userMapper.getUserByLimit(map);
for (User user : list) {
System.out.println(user);
}
}
5. 配置解析
5.1 核心配置文件
- mybatis-config.xml
- mybatis的配置文件包含了mybatis的行为设置和属性信息
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
5.2 属性 (properties)
properties
配置文件形式注册数据库信息
<properties resource="db.properties"/>
5.3 类型别名(typeAliases)
typeAliases
给实体类设置别名(不区分大小写),mapper中的返回值类型可以直接写别名@Alias("user")
或者可以在实体类上用注解形式起别名(需要在typeAliases下的package中加入需要扫描的包)package
在没有注解的情况下会使用Bean的首字母小写的非限定类名来作为他的别名
<!--第一种-->
<typeAliases>
<typeAlias type="com.ning.pojo.User" alias="user"/>
</typeAliases>
<!--第二或第三种-->
<typeAliases>
<package name="com.ning.pojo"/>
</typeAliases>
5.4 映射器(mappers)
作用:定义映射SQL语句文件
- 使用相对于类路径的资源引用
<mappers>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
- 使用完全限定资源定位符(URL)
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
- 使用映射器接口实现类的完全限定类名(需要配置文件名称和接口名称一致,并且位于同一目录下)
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
- 将包内的映射器接口实现全部注册为映射器(需要配置文件名称和接口名称一致,并且位于同一目录下)
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
5.5 设置日志(log4j)
- 在mybatis-config.xml中设置日志工具
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
- 导入log4j的包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- log4j.properties
### 设置###
log4j.rootLogger = debug,stdout,D,E
### 输出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = E://logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
### 输出ERROR 级别以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
5.6 设置下划线驼峰自动转换
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
6. Mybatis注解
缺点明显,只能用于简单的sql语句
@Select()
用在接口中的需要查询的方法上
@Select("select * from user")
List<User> getUserAll();
@Param
给参数命名,基本类型的参数或者string类型需要加上,引用类型不需要加
@Select("select * from user where id=#{id}")
List<User> getUserById(@Param("id") int id);
@Insert
@Insert("insert into user(id,name,password) values (#{id},#{name},#{password})")
int addUser(User user);
@Update
@Update("Update user set name = #{name},password = #{password} where id = #{id}")
int updateUserById( User user);
7. 多对一
以学生表和老师表为例,多名学生包含一位老师的tid;
利用ResultMap中的association
标签,映射实体类中的对象属性
7.1 按查询嵌套处理(子查询)
- 查询tid为1的老师拥有的所有学生的学生信息
<?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.ning.dao.StudentMapper">
<!--映射结果集-->
<resultMap id="studentMap" type="student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!-- property对应着student实体类中的teacher对象,javaType为对应的实体类,select要连接查找的查询语句-->
<association property="teacher" column="tid" javaType="Teacher" select="findTid"/>
</resultMap>
<!--查询student所有的信息,包括老师的信息,返回值为上面映射的结果集-->
<select id="findAll" resultMap="studentMap">
select * from student where tid = #{tid}
</select>
<select id="findTid" resultType="Teacher">
select * from teacher
</select>
</mapper>
运行测试
public class MapperTest {
SqlSession sqlSession = MybatisUtils.getSession();
TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
@Test
public void test(){
List<Student> students = studentMapper.findAll(1);
for (Student student : students) {
System.out.println(student);
}
sqlSession.close();
}
}
结果为
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='秦老师'}}
7.2 按结果嵌套处理(联合查询)
<!--按结果嵌套处理-->
<resultMap id="StudentTeacher2" type="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<!--关联对象property 关联对象在Student实体类中的属性-->
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
<select id="findAll2" resultMap="StudentTeacher2" >
select s.id sid, s.name sname , t.name tname from student s,teacher t where s.tid = t.id
</select>
8. 一对多
一名老师包含了多个学生,首先在实体类中,学生不是个体的存在而是一个集合
Teacher实体类
public class Teacher {
private Integer id;
private String name;
private List<Student> students;
}
8.1 按查询嵌套处理(子查询)
<resultMap id="teacherMap2" type="com.ning.pojo.Teacher">
<collection property="students" ofType="Student" select="findStu" column="id"/>
</resultMap>
<select id="findTeaById" resultMap="teacherMap2">
select * from teacher where id = #{tid}
</select>
<!-- 这里的tid是从上面查老师那边传过来的 -->
<select id="findStu" resultType="Student">
select * from student where tid = #{tid}
</select>
8.2 按结果嵌套处理(联合查询)
<!--一对多 集合,使用collection-->
<!-- JavaType和ofType都是用来指定对象类型的
JavaType是用来指定pojo中属性的类型
ofType指定的是映射到list集合属性中pojo的类型
-->
<resultMap id="teacherMap" type="com.ning.pojo.Teacher">
<result column="tname" property="name"/>
<result column="id" property="id"/>
<collection property="students" ofType="Student">
<result column="sid" property="id"/>
<result column="sname" property="name"/>
<result column="tid" property="tid"/>
</collection>
</resultMap>
<select id="findStu" resultMap="teacherMap">
select s.id sid ,s.name sname,t.name tname from teacher t,student s where t.id = s.tid
</select>
运行结果
Teacher{id=null, name='秦老师',
students=[Student{id=1, name='小明'},
Student{id=2, name='小红'},
Student{id=3, name='小张'},
Student{id=4, name='小李'},
Student{id=5, name='小王'}]}
9. 多对一、一对多(不同点)
- 关联-association
- 集合-collection
- 所以association是用于一对一和多对一,而collection是用于一对多的关系
- JavaType和ofType都是用来指定对象类型的
JavaType是用来指定pojo中属性的类型
ofType指定的是映射到list集合属性中pojo的类型。
10. 动态SQL
根据不同的条件生产不同的sql语句
10.1 < where > < if > 多条件判断
<select id="findBlog" resultType="Blog" parameterType="com.ning.pojo.Blog">
select * from blog
<where>
<!--如果blog类中的title属性不为空则去进行条件查询-->
<if test="title!=null">
title=#{title}
</if>
<!--如果这个条件也成立就进行拼接-->
<if test="author!=null">
and author=#{author}
</if>
</where>
</select>
10.2 < choose > < when > < otherwise > 多条件判断
<select id="findBlog" parameterType="com.ning.pojo.Blog" resultType="Blog">
select * from blog
<where>
<choose>
<!--如果条件成立则拼接-->
<when test="title!=null">
title=#{title}
</when>
<!--从第二个拼接开始都要加 and -->
<when test="author!=null">
and author=#{author}
</when>
<!--如果以上条件都不成立,则进入-->
<otherwise>
and views=#{views}
</otherwise>
</choose>
</where>
</select>
10.3 < set > update使用
<update id="updateBlog" parameterType="com.ning.pojo.Blog">
update blog
<set>
<if test="title!=null">
title=#{title},
</if>
<if test="author!=null">
author=#{author}
</if>
</set>
where id=#{id}
</update>
10.4 < sql > < include > 复用代码
抽取重复的代码,类似封装
<!--把重复的if判断封装起来-->
<sql id="if-title-author">
<if test="title!=null">
title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</sql>
<select id="findBlog1" resultType="Blog" parameterType="com.ning.pojo.Blog">
select * from blog
<where>
<!--这里只需要导入上面的id就可以-->
<include refid="if-title-author"/>
</where>
</select>
10.5 < foreach >批量处理
<select id="findBlogInId" parameterType="list" resultType="Blog">
select * from blog
<where>
id in
<!--collection 为对应的集合名称-->
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</where>
</select>
11. 缓存
缓存只适用于经常查询,而不频繁改动的数据
11.1 一级缓存
-
一级缓存也叫本地缓存:SqlSession
-
与数据库同一次会话期间查询到的数据会放在本地缓存中
-
以后如果需要获取相同的数据,直接从缓存中拿不需要再去查询数据库
-
11.1.1 四种导致一级缓存失效的情况
- 不是同一个SqlSession
- SqlSession相同,但是查询条件不同
- SqlSession相同,两次查询中间有增删改操作
- SqlSession相同,两次查询中间清空了一级缓存
11.2 二级缓存
- 二级缓存也叫全局缓存
- 基于namespace级别的缓存,一个名称空间对应着一个二级缓存
- 工作机制
- 一个会话查询一条数据,这个数据就会放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存就没了
- 如果会话关闭时,一级缓存的数据被保持在二级缓存中,新的会话进来也可以从二级缓存中获取到数据
11.2.1 使用步骤
- 在mybatis-conifg.xml中加入设置,开启全局缓存
<setting name="cacheEnabled" value="true"/>
- 在每个mapper.xml中配置二级缓存
<!--开启二级缓存-->
<cache/>
<!--更细节的配置,开启二级缓存的同时,创建了一个 FIFO 缓存,每隔 60 秒刷新,
最多可以存储结果对象或列表的 512 个引用,
而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者 产生冲突。-->
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>