MyBatis
框架:SSM:spring springMVC mybatis(特点:配置文件很多)
spring boot
spring cloud
1、什么是mybatis
持久层框架,与数据库相关的操作(dao层)可以使用mybatis来实现
2、为什么要用mybatis
频繁连接释放资源
封装查询结果
之前的方式存在sql硬编码问题(如果sql发送改变,需要去修改java代码,项目需要重新编译/打包/…)
解决sql硬编码问题:把sql语句和java代码分离
把sql语句单独的写在一个文件中(.xml),把写sql的xml文件叫做sql映射文件
3、执行流程
了解mybatis的执行流程,能够更好的去理解代码
1、加载mybatis核心配置文件(比如:数据连接信息/数据源信息/…)
sql映射文件:写sql语句
2、读取sql映射文件
sqlSessionFactroy工厂,生成SqlSession对象
SqlSession对象,可以去执行sql
提供很多增删改查方法
**注意:**工厂在生成SqlSession需要用配置信息
3、mybatis中的核心API
SqlSessionFactory:生成SqlSession对象
SqlSession:执行sql
4、mybaits快速入门
1、添加依赖
在recources文件下创建sqlMapConfig.xml
mybaits核心配置从官网拷贝过来就可以了
<?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"/>
<property name="url" value="${url}"/>
<property name="username" value="root"/>
<property name="password" value="root}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 添加sql -->
<mapper resource="com.ycy.mapper.UserMapper"/>
</mappers>
</configuration>
2、sql映射文件
student:
学生相关sql—>Student.xml
user:
用户相关sql—>user.xml
class:
班级相关sql—>class.xml
创建操作user的sql映射文件:
UserMapper.xml
创建mybatis核心配置文件:
在resources目录下创建
把下面的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="UserMapper">
<select id="findAll" resultType="com.ycy.entity.User">
select * from tb_user
</select>
</mapper>
测试
public void test(){
// //加载核心配置文件
// InputStream reader= MyTest.class.getClassLoader().getResourceAsStream("sqlMapConfig.xml");
// //获取sqlSessionFactory
// SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);//构建工厂需要配置信息
// //根据工厂获取SqlSession对象
// SqlSession sqlSession = factory.openSession();
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//使用sqlSession对象执行sql
List<User> list = sqlSession.selectList("UserMapper.findAll");参数:代表要执行的sql的唯一标识--->namespace.id
//打印
for (User user : list) {
System.out.println(user);
}
}
3、使用方式(步骤):
1、写核心配置文件
2、写sql映射文件
3、创建factory工厂对象
4、创建sqlSession对象
5、通过sqlSession对象执行sql
4、优化:
1、工具类:获取sqlSession对象
public class MyBatisUtils {
//工厂只能有一个
private static SqlSessionFactory factory;
static{
//加载核心配置文件
InputStream reader= MyBatisUtils.class.getClassLoader().getResourceAsStream("sqlMapConfig.xml");
//获取sqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);//构建工厂需要配置信息
}
//提供一个伊公共的静态的方法,返回sqlSession对象
public static SqlSession getSqlSession(){
//根据工厂获取SqlSession对象
SqlSession sqlSession = factory.openSession();
return sqlSession;
}
}
2、执行sql的方式
sqlSession.selectList("UserMapper.findAll");//namespance.id
回到以前熟悉的操作方式
UserDao:
findAll()
userDao.findAll()
(1)创建接口
在mybatis中,dao层的包名一般叫mapper
UserDao—>UserMapper
创建UserMapper接口
(2)创建接口对应的sql映射文件
在resources目录下,创建同路径同名的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.ycy.mapper.UserMapper">
<select id="findAll" resultType="com.ycy.entity.User">
select * from tb_user
</select>
</mapper>
namespace的值是对应接口的权限命名:包名.类名
id值对应接口中的方法名
测试:
public void test(){
//根据工厂获取SqlSession对象
// SqlSession sqlSession = factory.openSession();
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//获取接口,调用接口中方法
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.findAll();
//打印
for (User user : list) {
System.out.println(user);
}
}
3、优化后的使用步骤
(1)创建工具类
(2)通过工具类拿到SqlSession
(3)使用SqlSession拿到Mapper接口
(4)调用Mapper接口中的方法
5、MyBatis核心配置文件
- configuration(配置)---->根标签
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
**注意:**子标签必须按照固定的顺序来写
1、properties(属性)
指定外部文件路径
2、settings(设置)
缓存设置
延迟加载设置
…
3、typeAliases(类型别名)
设置别名
方式一:一个一个的设置(麻烦)
<!-- 设置别名-->
<typeAliases>
<typeAlias type="com.ht.entity.User" alias="user"/>
</typeAliases>
方式二:自动扫描(自动取别名):名字只要实体类的名字字母一样即可(不区分大小写)
<typeAliases>
<package name="com.ht.entity"/>
</typeAliases>
4、environments(环境配置)
可以配置多个环境
<environments default="mysql1">//选择使用的环境(选择环境id)
<environment id="mysql1">//配置某个具体的环境,给一个id
<transactionManager type="JDBC"/>//设置事务管理器
<dataSource type="POOLED">//设置数据源:
//POOLED:使用带连接池的数据源,可以换成其他的连接池
<property name="driver" value="${jdbc.driver}"/>//数据库连接信息
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
如何更新数据源:
1、添加依赖
<!--druid数据源 pom.xml(添加依赖)-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
2、构建数据源适配器
//创建数据源适配器
pulibc class DruidDataSourceFactory extends UnpooledDataSourceFactory{
//构造方法
public DruidDataSourceFactory(){
//指定数据源
this.dataSource = new DruidDataSource();
}
}
3、修改数据源配置
<environments default="mysql1">
<!-- 第一个环境 -->
<environment id="mysql1">
<transactionManager type="JDBC"/>使用自定义数据源适配器
<dataSource type="com.ht.factory.DruidDataSourceFactory">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
5、mappers(指定映射文件路径)
<!--添加sql映射文件路径-->
<mappers>
<!-- <mapper resource="com/ycy/mapper/UserMapper.xml"/>-->//方式一:指定xml文件的路径
<!--<mapper class="com.ht.mapper.UserMapper"/>-->//方式二:直接Mapper接口路径
<package name="com.ht.mapper"/>//方式三:自动扫描(指定扫描路径)
</mappers>
6、sql映射文件
写sql:增删改查
XML映射器
select
insert,update和delete
<select>查询</select>
<insert>增加</insert>
<update>修改</update>
<delete>删除</delete>
1、添加数据
<!--添加-->
<insert id = "addUser" parameterType="User">
insert into tb_user(username,password) values(#{username},#{password})
</insert>
**注意:**现在执行的是更新操作,但是没有提交事务
//提交(手动提交)
sqlSession.commit();//提交事务
sqlSession.close();//关闭
//自动提交
//在获取sqlSession对象时
//如果openSession()中没有传递参数,关闭自动提交
public static SqlSession getSqlSession(){
//根据工厂获取SqlSession对象
//没有自动提交
//SqlSession sqlSession = factory.openSession();
//开启自动提交
SqlSession sqlSession = factory.openSession(true);
return sqlSession;
}
如果想要获取添加数据后的id:
把id值赋值给参数的属性
<!--添加-->
<insert id = "addUser" parameterType="User" useGeneratedKeys = "true" keyColumn="id" keyProperty="id">
insert into tb_user(username,password) values(#{username},#{password})
</insert>
2、修改数据
<update id = "updateUser" parameterType = "User">
update tb_user set username = #{username},password=#{password} where id = #{id}
</update>
3、删除数据
<delete id = "deleteById" parameterType="int">
delete * from tb_user where id = #{id}
</delete>
parameterType:
简单数据类型的别名
参数类型 别名
int _int
short _short
…
String string
Integer int
Short short
4、查询数据
<select id = "findById" parameterType = "int" resultType="User">
select * from tb_user where id = #{id}
</select>
resultType:
指定返回值类型
把查询结果自动封装到指定的返回值类型中
自动封装:字段名和属性名保持一致
字段名—>属性名
字段名不一致如何解决?resultMap
7、自定义映射标签
问题:字段名和属性名不一致,无法完成自动映射(封装)
解决:自定义映射关系
resultMap:自定义映射关系
<resultMap id = "stdMap" type = "Student">
<!--主键-->
<id column="id" property="xh">
<!--非主键-->
<result column = "username" property = "stdName"/>
<result property = "gender" column = "sex"/>
<result property = "stdAge" column = "age"/>
</resultMap>
<select id = "findById" parameterType = "int" resultMap="stdMap">
select * from tb_user where id = #{id}
</select>
resultType:使用默认的映射关系(字段名和属性名一致),默认的映射关系不能满足,所以要使用自定义的映射关系(不能用resultType属性)
使用resultMap标签去自定义映射关系
使用resultMap属性去指定要使用的自定义映射关系
8、参数传递
1、一个参数
一个简单类型的参数
在sql中占位符中间的名字随便写,一般和参数名保持一致
2、多个参数
解决方案一:使用@Param注解
占位符中的名字要和注解里面的名字一致
User find (@Param("username") String username,@Param("password") String password)
解决方案二:把参数封装到map集合中
占位符中的名字要和map集合中的key值一致
User findByParamMap(Map<String,Object> map);
解决方案三:参数封装到实体类中
占位符中名字和实体类中属性保持一致
9、动态sql
根据传递的不同参数,动态的拼接成不同的sql
//StudentMapper.java
//条件查询:所有查询条件封装到Student对象中
List<Student> findByCondition(Student student);
需要根据参数的情况进行判断:
如果username,sql中就把username拼接上
如果sex,在sql中就把sex拼接上
…
1、< if >判断
<if test="判断条件">满足条件执行的内容</if>
<if test="username!=null">
username = #{...}
</if>
<if test="sex!=null">
sex = #{...}
</if>
<!--StudentMapper.xml-->
<mapper>
<select id = "findByCondition" parameterType="Student" resultMap="stdMap">
select* from tb_student where 1=1
<if test= "xh!=null">
and id = #{xh}
</if>
<if test="stdName!=null">
and username=#{stdName}
</if>
<if test="gender!=null">
and sex=#{gender}
</if>
<if test="classno!=null">
and classno=#{classno}
</if>
</select>
</mapper>
Mybatis查询sql(日志)
在resources下添加log4j.properties文件
#全局日志配置
log4j.rootLogger=ERROR,stdout
#MyBatis 日志配置
log4j.logger.com.ycy.mapper=DEBUG
#控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=ort.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] -%m%n
2、< where >
在sql后面拼接where关键字,会自动的把多余的and去掉
<!--StudentMapper.xml-->
<mapper>
<select id = "findByCondition" parameterType="Student" resultMap="stdMap">
select* from tb_student
<where>
<if test= "xh!=null">
id = #{xh}
</if>
<if test="stdName!=null">
username=#{stdName}
</if>
<if test="gender!=null">
sex=#{gender}
</if>
<if test="classno!=null">
classno=#{classno}
</if>
</where>
</select>
</mapper>
3、< set >
相当于在sql后面拼接set关键字,并且会自动的把多余的and去掉
<update id = "updateStudent" parameterType = "Student">
update tb_student
<set>
<if test = "stdName!=null">
username = #{stdName}
</if>
<if test = "gender!=null">
sex = #{gender}
</if>
<if test = "stdAge!=null">
age=#{stdAge}
</if>
<if test="classno!=null">
classno=#{classno}
</if>
</set>
</update>
4、< foreach >
用来循环的
<!--批量删除-->
<delete id = "batchDel">
delete * from tb_student where id in
<foreach collection = "array" item = "id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
属性:
open:数组的开始标志
separator:数组之间元素的分隔
close:数组的结束标志
collection:循环的数据类型:array(数组) /list(list集合)
foreach:把循环出来的数据保存到一个变量中
#{id}:使用循环出来的数据(变量id)
10、关联查询
1、一对一
Person --添加–> IdCard
public class Person{
private Integer id;
private String name;
private String sex;
private Integer age;
private IdCard idCard;
}
//方式一:根据id查询用户信息(查询出身份证信息)
Person findById(Integer id);
//方式二:根据id查询用户的信息
Person findById2(Integer id);
<mapper namespace="com.ycy.mapper.PersonMapper">
<resultMap id = "personAndCard" type="Person">
<id property = "id" column="id"/>
<result property = "name" column = "name"/>
<result property = "sex" column = "sex"/>
<result property = "age" column = "age"/>
<association property = "idCard" javaType = "IdCard" selet = "com.ycy.mapper.IdCardMapper.findByPersonId" column="id"/>
</resultMap>
<select id = "findById" parameterType="int" resultType="Person">
select * from tb_person
<where>
id=#{id}
</where>
</select>
</mapper>
<select id="findByPersonId" parameterType="int" resultType="Idcard">
select b.*
from tb_person a,tb_idcard b
where a.card_id = b.id and a.id=#{id}
</select>
实现1对1的关联查询:
需要使用< association >
**注意:**在< resultMap>中使用
实现:
方式一(分步查询):去写一个person的id查询idCard的信息的sql,然后去调用即可(执行2个sql)
方式二(嵌套结果):一次性把数据查询出来
<resultMap id="personAndCard2" type="Person">
<id property="id" column="id"/>
<result property="name" column ="name"/>
<result property = "sex" column = "sex"/>
<result property = "age" column = "age"/>
<association property = "idCard" javaType ="IdCard">
<id property="id" column="card_id"/>
<result property = "cardnum" column="cardnum"/>
</association>
</resultMap>
<select id = "findById2" parameterType = "int" resultMap="personAndCard2">
select a.*,b.cardnum
from tb_person a,tb_idcard b
where a.card_id = b.id and a.id=#{id}
</select>
2、一对多
方式一:(不好用不记了)
方式二:一次性查询出所有需要的数据
public class Student2{
private Integer id;
private String username;
private String sex;
private Integer age;
}
public class Classinfo{
private Integer id;
private String classname;
List<Student> students;
}
public interface ClassinfoMapper{
//根据id查询班级信息(把班级学生信息查询出来)
Classinfo findById(Integer id);
}
<mapper namespace="com.ycy.mapper.ClassinfoMapper">
<!--根据id查询班级信息-->
<resultMap id="classAndStd" type="Classinfo"/>
<id property = "id" column ="classno"/>
<result property = "classname" column="classname"/>
<collection property = "student" ofType="Student">
<id property = "id" column = "id"/>
<result property ="username" column = "username"/>
<result property = "sex" column = "sex"/>
<result property = "age" column = "age"/>
</collection>
<select id = "findById" parameterType = "int" resultType="Classinfo">
select a.classname,b.*
from tb_class a,tb_student b
where a.id=b.classno and a.id=#{id}
</select>
</mapper>
标签:
< collection> -->自定义一对多映射关系
**注意:**在< resultMap>中使用
3、多对多
悟
11、MaBatis缓存和延迟加载
1、MaBatis缓存
1.一级缓存
- 第一次查询 ,首先会从一级缓存中查找有没有想要查询的数据,如果没有,这时从数据库查询,查询之后把查询结果保存到一级缓存中
- 第二次查询,仍然会从一级缓存中查找,发现一级缓存中有,直接从一级缓存中拿
总结:1、查询数据先从缓存中进行查询,如果缓存中有,直接使用,不云查询数据库
2、如果缓存中没有,在从数据库查询数据,并查询到的数据保存到缓存中
3、如果执行的更新操作(insert /update/delete),会清空缓存
4、一级缓存是基于SqlSession级别
5、在同一个SqlSession下的不同的Mapper共用同一级缓存
6、不同的SqlSession下,会再次执行sql语句
2.二级缓存(namespace)
**注意:**二级缓存默认关闭,需要开启,(一般没有特殊需求,不开启二级缓存)
**注意:**二级缓存的数据需要进行序列化
第一步:核心配置文件中
<!--sqlMapConfig下-->
<setting>
<!--开启二级缓存(全局)-->
<setting name = "cacheEnabled" value= "true"/>
</setting>
第二步:sql映射文件
<!--开始局部二级缓存-->
<cache></cache>
第三步:序列化实体类
注意:一级缓存缓存的是对象(缓存的哪个对象,拿出来的就是哪个对象)
二级缓存缓存的是数据(数据进行序列化,保存的拿出来的不是同一个对象)
2、延迟加载(采用分步查询)
如果需要查询出关联的数据,叫做立即加载
如果不需要查询出关联的数据,叫做延迟加载(懒加载)
一般而言:
- 一对一:采用立即加载
- 一对多:采用延迟加载
延迟加载特点:
当你采用延迟加载策略,如果没有使用到关联数据,不会进行关联查询,如果使用到了关联数据,会查询关联数据(按需要加载)
开启延迟加载:
核心配置文件
<setting>
<!--开记延迟加载(全局)-->
<setting name="lazyLoadingEnabled" value="true"/>
</setting>
设置局部的加载策略
<result property = "age" column = "age"/>
<association property = "idCard" javaType = "IdCard" select ="com.ycy.mapper.IdCardMapper.findByPersonId" column = "id" fetchType="eager"
总结:延迟加载的效果
开启全局的延迟加载
设置局部的延迟加载策略
3、分页插件
方便的查询分页数据
使用:
1、添加分页插件(依赖:pom.xml)
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
2、设置分页插件(核心配置文件中)
<!--引入pageHelper插件-->
<!--注意这里要写成PageInterceptor,5.0之前的版本都是写PageHelper,5.0之后要换成PageInterceptor-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--reasonable:分页合理化参数,默认值为false,当该参数设置为true时,pageNum<=0时会查询第一页,pageNum>pages(超过总数时),会查询最后一页。-->
<property name ="helperDialect" value="mysql"/>
<property name="reasonable" value="true"/>
</plugin>
</plugins>
3、进行分页查询
提供一个查询所有数据的方法即可
Lisr<Student> findAll();
<!--查询所有-->
<select id = "findAll" resultType="stdMap">
select * from tb_student
</select>
先
@Test
public void test(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//这是分页信息
//startPage(int pageNum,int pageSize)
//pageNum:当前页
//pageSize:每页显示多少条数据
PageHelper.startPage(2,3);
//调用查询所有数据的方法s
List<Student> all = mapper.findAll();
//把分页数据封装到一个对象中
PageInfo<Student> pageInfo = new PageInfo(all);
System.out.println(pageInfo)
sqlSession.close();
}