MyBatis 学习笔记
简介:
- 是一个基于java的持久层框架,支持自定义SQL,存储过程以及高级映射
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO映射为数据库中记录
- Mybatis是一个半自动的ORM框架
mybatis下载:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
比较
- JDBC:
- SQL夹杂在java代码中,耦合度比较高,导致编码内伤
- 维护不易且实际开发需求中SQL有变化,频繁修改的情况多见
- 代码冗长,开发效率低
- Hibernate和JPA
- 操作简单,开发效率高
- 程序中的长复杂的SQL需要绕过框架
- 内部自动生产SQL,不容易做特殊持久化
- 基于全映射的全自动框架,大量字段的POJO进行部分映射比较困难
- 反射操作太多,导致数据库性能下降
- Mybatis
- 轻量级
- SQL和java编码分开,功能边界清晰,java注重业务开发,SQL语句专注数据
- 开发效率低于Hiberna,但是完全能够接收
创建核心配置文件
配置连接数据的信息,以及Mybatis的全局配置信息 映射文件中主要写SQL语句
<?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">
<!--mybatis的约束-->
<!--根标签-->
<configuration>
<!--environments连接数据库的环境-->
<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/demo01"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<!--设置具体的环境-->
</environments>
<!--mappers:引入映射文件-->
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
步骤1、创建mapper接口
相当于dao接口,mapper仅仅是接口,没有实现类
public interface UserMapper {
/*
* Mybatis面向接口编程的两个一致
* 1、namespace与Mapper接口的全类名一致
* 2、映射文件中的SQL语句id和Mapper接口中的方法名一致
*/
/*
* 添加用户信息
* */
int insertUser();
}
步骤2、创建映射文件
ORM:对象关系映射:Java的实体类对象,关系型数据库,二者之间的对应关系。
- 映射文件创建规则:表对应的实体类的类名+Mapper.xml,因此一个映射文件对应一个实体类,对应一张表的操作。
- 映射文件: 1、namespace与Mapper接口的全类名一致 2、映射文件中的SQL语句id和Mapper接口中的方法名一致
<?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="Mapper.UserMapper">
<insert id="insertUser">
insert into t_user values (null,'admin','12345',23,'男','12345@qq.com')
</insert>
</mapper>
步骤3:引入映射文件:
<?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">
<!--mybatis的约束-->
<!--根标签-->
<configuration>
<!--environments连接数据库的环境-->
<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/demo01"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<!--设置具体的环境-->
</environments>
<!--mappers:引入映射文件-->
<mappers>
<mapper resource="Mapper/UserMapper.xml"/>
<!--以路径的方式引入文件-->
</mappers>
</configuration>
创建测试类
- sqlsession:代表程序和数据库之间的绘画
- sqlsessionFactory:是生产sqlsession的工厂
- 工厂模式:如果创建一个对象,使用的过程基本固定,那么我们就可以把创建对象的相关代码封装到一个工厂类中,以后都使用这个工厂类进行生产我们需要的对象。
@Test
public void test01() throws IOException {
//加载核心配置文件
InputStream stream = Resources.getResourceAsStream("mybatis_config.xml");
//获取sqlSessionFactoryBuilder
SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
//获取sqlsessionFactory
SqlSessionFactory sqlSessionFactory = factoryBuilder.build(stream);
//获取sqlsession
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取mapper接口对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//测试功能
int i = mapper.insertUser();
System.out.println(i);
}
提交事务,才会有效果:
System.out.println(i);
//提交事务
sqlSession.commit();
sqlsession默认不提交事务,
开启自动提交功能。
log4j日志
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//log4j/log4j Configuration//EN" "log4j.dtd">
<log4j:configuration>
<!--输出到控制台 -->
<appender name="consoleAppender" class="org.apache.log4j.ConsoleAppender">
<param name="Threshold" value="WARN" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" />
</layout>
</appender>
<!--输出到文件(info) -->
<!--将生成“info.log.2014-06-11”这样的日志文件 -->
<appender name="fileAppenderInfo" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="${user.home}/logs/info.log" />
<param name="DatePattern" value=".yyyy-MM-dd" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" />
</layout>
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="LevelMin" value="INFO" />
<param name="LevelMax" value="INFO" />
</filter>
</appender>
<!--输出到文件(warn) -->
<appender name="fileAppenderWarn" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="${user.home}/logs/warn.log" />
<param name="DatePattern" value=".yyyy-MM-dd" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" />
</layout>
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="LevelMin" value="WARN" />
<param name="LevelMax" value="WARN" />
</filter>
</appender>
<!--输出到文件(error) -->
<appender name="fileAppenderError" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="${user.home}/logs/error.log" />
<param name="DatePattern" value=".yyyy-MM-dd" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" />
</layout>
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="LevelMin" value="ERROR" />
<param name="LevelMax" value="ERROR" />
</filter>
</appender>
<!--屏蔽所有org.springframework.*输出的Debug(及以下)信息 -->
<logger name="org.springframework">
<level value="INFO"></level>
</logger>
<root>
<level value="ALL" />
<appender-ref ref="consoleAppender" />
<appender-ref ref="fileAppenderInfo" />
<appender-ref ref="fileAppenderWarn" />
<appender-ref ref="fileAppenderError" />
</root>
</log4j:configuration>
测试查询功能:必须设置ResultType或者ResultMap
- ResultType:结果类型,对应的实体类的对象。设置默认的映射关系
通过字段名和属性名进行映射 - ResultMap:结果映射,设置默认的自定义的关系
<select id="selectById" resultType="POJO.User">
select * from t_user where id=1;
</select>
查询所有的返回list集合
核心配置文件
配置文件的顺序: properties、settings、typeAliases、typeHandlers、objectFactory、objectWrapperFactory、reflectorFactory、plugins、environments、databaseIdProvider、mappers
environment
配置多个连接数据库的环境
<!--environments连接数据库的环境-->
<environments default="development">
<!--defualt:配置默认使用的环境的id-->
<environment id="development">
<!--id唯一标识,不能重复,表示当前连接的环境-->
<transactionManager type="JDBC"/>
<!--事务管理的方式:JDBC/Managed
JDBC:表示操作数据库的时候,使用JDBC的原生的事务管理方式,事物的提交和回滚需要手动进行处理
Managed:表示被管理,例如spring
-->
<dataSource type="POOLED">
<!--
数据源,type设置数据源的类型
POOLED:使用数据库连接池缓存数据库连接
UNPOOLED:不使用数据库连接池
JNDI:使用上下文的数据源
-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<!--设置具体的环境-->
</environments>
<!--mappers:引入映射文件-->
properties文件
编写properties文件
引入properties文件
读取properties文件数据
typeAliases 类型别名的功能
<!--设置类型别名-->
<!--通过类型代表全类名-->
<!--
类型别名不区分大小写
可以不设置alias:名字会设置为默认的类名,且不区分大小写
-->
<typeAliases>
<typeAlias type="POJO.User" alias="User"/>
</typeAliases>
<typeAliases>
<!-- <typeAlias type="POJO.User" alias="User"/>-->
<!--以包为单位,将包下所有的类型设置为默认的类型别名,即类名且不区分大小写-->
<package name="POJO"/>
</typeAliases>
mappers 引入映射文件
<mappers>
<!-- <mapper resource="Mapper/UserMapper.xml"/>-->
<!--以路径的方式引入文件-->
<!--以包为单位引入-->
<!--
要求:
mapper接口所在的包和映射文件所在的包一致
mapper接口要和映射文件的名字一致
-->
<package name="Mapper"/>
</mappers>
<?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">
<!--mybatis的约束-->
<!--根标签-->
<configuration>
<!--读取文件-->
<properties resource="jdbc.properties"/>
<!--设置类型别名-->
<!--通过类型代表全类名-->
<!--
类型别名不区分大小写
如果不设置alias:名字会设置为默认的类名,且不区分大小写
-->
<typeAliases>
<!-- <typeAlias type="POJO.User" alias="User"/>-->
<!--以包为单位,将包下所有的类型设置为默认的类型别名,即类名且不区分大小写-->
<package name="POJO"/>
</typeAliases>
<!--environments连接数据库的环境-->
<environments default="development">
<!--defualt:配置默认使用的环境的id-->
<environment id="development">
<!--id唯一标识,不能重复,表示当前连接的环境-->
<transactionManager type="JDBC"/>
<!--事务管理的方式:JDBC/Managed
JDBC:表示操作数据库的时候,使用JDBC的原生的事务管理方式,事物的提交和回滚需要手动进行处理
Managed:表示被管理,例如spring
-->
<dataSource type="POOLED">
<!--
数据源,type设置数据源的类型
POOLED:使用数据库连接池缓存数据库连接
UNPOOLED:不使用数据库连接池
JNDI:使用上下文的数据源
-->
<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>
<!--设置具体的环境-->
</environments>
<!--mappers:引入映射文件-->
<mappers>
<!-- <mapper resource="Mapper/UserMapper.xml"/>-->
<!--以路径的方式引入文件-->
<!--以包为单位引入-->
<!--
要求:
mapper接口所在的包和映射文件所在的包一致
mapper接口要和映射文件的名字一致
-->
<package name="Mapper"/>
</mappers>
</configuration>
映射文件如何进行拼接(获取参数的两种方式)
- 普通的JDBC
- 字符串拼接:sql注入,操作麻烦
- 占位符赋值:?,使用setString等方法为预编译的preparesStatement进行赋值,自动加上单引号,可以避免SQL注入
- Mybatis
- ${}:字符串拼接
- #{}:占位符赋值 @Param标签
各种查询功能
- 查询实体类对象,多条对象不能用实体类对象接收----toomanyResultException
- 查询list集合.可以接收一条甚至为空的形式来进行接收
- 聚合函数,分组函数:查询用户信息的总记录数,基本类型有默认的类型别名。
- map集合:k-v:属性名对应属性值的形式;json对象;当有多个对象的时候,会有异常,当有多条数据的时候,要使用list集合
- map集合可以存储多条数据,另外给他设定一个键,这样vlaue是一条数据,key不能重复
@MapKey("id")
Map<String,Object> getUserByIdToMap(@Param("id") Integer id);
mybatis特殊sql的执行(使用#会有问题)
#{}会自动添加一个‘’
- 模糊查询: 使用#{}时,sql语句,所以要使用${},或者进行手动的字符串拼接
select * from t_user where username like '%?%'
concat('%',#{username},'%')
最多的应用方式
"%"#{username}"%"
- 批量删除 如果使用#{id},会有单引号,所以我们应该直接使用${}
- 动态设计表名:将一张表拆分成多张表 这里的表名的拼接不可以使用${}
- 添加功能获取自增的主键:有一对多的关系,为一个表分配多的关系,eg,为学生设置班级id,useGeneratedKeys设置当前标签中的sql使用了自增的id,keyProperty将自增的组件的值赋值给传输到映射文件中,参数的某个属性。
ResultSet generatedKeys = insert.getGeneratedKeys();
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values (null,#{username},#{password},#{age},#{sex},#{email})
</insert>
自定义映射ResultMap
- 驼峰和下划线进行适配:如果直接使用resulttype进行返回类型,有不符合的时候---:三种方法
- 为字段起别名
<select id="getAllEmp" resultType="emp">
select eid,emp_name empName,age,sex,email from t_emp
</select>
- 全局配置:将下划线自动映射成驼峰
<!--设置全局配置,作用与全部功能-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
- resultMap:设置自定义映射关系,id是唯一标识,type表示映射的那个实体类类型,id子标签设置主键的映射,result设置普通的映射,property设置映射关系中的实体类的属性名,column是设置映射关系中sql数据库的字段名。使用resultmap处理多对一的映射
<resultMap id="empResultMap" type="emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</resultMap>
<select id="getAllEmp" resultMap="empResultMap">
select * from t_emp
</select>
处理多对一的映射关系
- 级联属性赋值
<resultMap id="empAndDeptResultMapOne" type="emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<result property="dept.did" column="did"></result>
<result property="dept.deptName" column="dept_name"></result>
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMapOne">
select *
from t_emp
left join t_dept on t_emp.did = t_dept.did
where t_emp.eid=#{eid};
</select>
- 第二种方式:association,专门用来处理多对一的映射关系,property表示需要处理的属性名(多的那个表)javatype这个属性的类型,通过反射获取赋值。
<association property="dept" javaType="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
</association>
- 分步查询,用的多,association的select:设置分步查询的sql的唯一标识,column:设置分步查询的条件
职工接口中
/*
* 通过分步查询来获取
* 员工信息
* */
emp getEmpAndDeptByStepOne(@Param("eid") int eid);
在职工表
<resultMap id="getEmpAndDeptByStepOne" type="emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<association property="dept"
select="com.demo3.Mapper.DeptMapper.getEmpAndDeptByStepTwo"
column="did"></association>
</resultMap>
<select id="getEmpAndDeptByStepOne" resultMap="getEmpAndDeptByStepOne">
select * from t_emp where eid=#{eid}
</select>
在部门接口
Dept getEmpAndDeptByStepTwo(@Param("did") int did);
在部门表
<resultMap id="Two" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
</resultMap>
<select id="getEmpAndDeptByStepTwo" resultMap="Two">
select * from t_dept where did = #{did}
</select>
延迟加载
分步查询可以实现延迟加载,默认不开启,必须在核心配置文件中设置全局信息
- lazyLoadingEnable:延迟加载的全局开关,所有关联对象都会延迟加载
- aggressiveLazyLoading:任何方法的调用都会加载该对象的所有属性,否则,每个属性按需加载。
处理一对多的关系
- collection:用来处理一对多的关系,oftype:集合中存储的数据的类型
private List<emp> emps;
/*
* 获取部门以及部门对应的所有员工信息
* */
Dept getDeptAndEmps(@Param("did") int did);
<resultMap id="DeptAndEmps" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<collection property="emps" ofType="emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"/>
<result property="age" column="age"></result>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
</collection>
</resultMap>
<select id="getDeptAndEmps" resultMap="DeptAndEmps">
select * from t_dept left join t_emp on t_emp.did=t_dept.did where t_dept.did=#{did}
</select>
- 分步查询 select:设置分步查询的sql的唯一标识,column设置查询的条件,fetchType:当开启全局的延迟加载之后,可通过这个属性手动控制延迟加载的效果。
Dept getDeptAndEmpsByStepOne(@Param("did") int did);
<select id="getDeptAndEmpsByStepOne" resultMap="one">
select * from t_dept where did =#{did}
</select>
<resultMap id="one" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<collection property="emps" select="com.demo3.Mapper.EmpMapper.getDeptAndEmpByStepTwo" column="did"></collection>
</resultMap>
emp getDeptAndEmpByStepTwo(@Param("did") int did);
<select id="getDeptAndEmpByStepTwo" resultMap="two">
select * from t_emp where did=#{did}
</select>
<resultMap id="two" type="emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</resultMap>
动态SQL
定义:根据特定条件,动态拼接sql语句的功能。
if标签
1=1:可以更好的拼接关键字,并且不会出现sql语句无条件,使where错误的情况
- if:根据test属性中的表达式,判断条件是否要进行拼接。
<select id="getEmpByCondition" resultType="emp">
select * from t_emp where 1=1
<if test="empName != null and empName!= ' '"> and emp_name=#{empName}</if>
<if test="age!=null and age!=' '">and age=#{age}</if>
<if test="sex!=null and sex!=' '">and sex=#{sex}</if>
<if test="email!=null and email!=' '">and email=#{email}</if>
</select>
where标签
当标签内有内容的时候,自动生成,当没有内容的时候,where标签不会自动生成该关键字,将内容前的多余的and,or去掉。不可以将内容后面的and,or去掉
<where>
<if test="empName != null and empName!= ' '"> and emp_name=#{empName}</if>
<if test="age!=null and age!=' '">and age=#{age}</if>
<if test="sex!=null and sex!=' '">and sex=#{sex}</if>
<if test="email!=null and email!=' '">and email=#{email}</if>
</where>
trim标签
- prefix|suffix:将trim标签中内容前后添加指定内容
- prefixOverrides|suffixOverrides:将trim标签中内容前后去掉指定内容
- 若标签中没有内容时,没有任何效果。
choose,when,otherwise
当有一个条件成立,后面的语句就不会进行拼接,相当于if...else if
<where>
<choose>
<when test="empName!=null and empName!=' '">emp_name=#{empName}</when>
<when test="age!=null and age!=' '">age=#{age}</when>
<when test="sex!=null and sex!=' '">sex=#{sex}</when>
<when test="email!=null and email!=' '">email=#{email}</when>
<otherwise>
eid=1
</otherwise>
</choose>
</where>
foreach标签
批量删除
int deleteMoreByArray(@Param("eids") int[] eids);
delete from t_emp where eid in
<foreach collection="eids" item="eid" separator="," open="(" close=")">
#{eid}
</foreach>
批量添加
int insertMore(List<emp> list);
<insert id="insertMore">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null,#{emp.empName}...)
</foreach>
</insert>
sql标签
抽取常用的sql的公共字段
<sql id="empColums">eid,empname....</sql>
<select id="...">
select <include refid="empColums"></include>
</select>
Mybatis的缓存
一级缓存
针对sqlsession级别,默认开启,同一个sqlsession查询相同数据,会有缓存,只对查询功能生效
一级缓存失效
- 不同的sqlsession
- 同一个sqlsession但是查询条件不同
- 两次查询之间,实现了增删改操作
- 同一个sqlsession两次查询期间手动清空了缓存(sqlsession.clearCache)
二级缓存
针对sqlSessionFactory级别,手动开启的,开启的条件:
- 在核心配置文件中,cacheEnable=true
- 在映射文件中设置标签
- 二级缓存只有在sqlsession关闭(close)或提交(commit)才会生效
- 查询的数据所转换的实体类类型必须实现serializable接口 使二级缓存失效的情况: 两次查询之间执行任意的增删改,会使一二级缓存失效。
cache标签属性针对二级缓存
- eviction:缓存回收策略:LRU,FIFO,SOFT,WEAK
- flushinterval:刷新间隔,单位毫秒(默认,调用增删改时刷新)
- size:引用数目,缓存存储多少对象
- readOnly:只读缓存,二级缓存会给缓存对象的相同实例,这些对象不可修改,会造成数据库不一致的情况;读写缓存,返回缓存对象的拷贝,(通过序列化)。
缓存查询顺序
- 先查询二级缓存,范围更大,命中率更高
- 二级缓存未命中,再查询一级缓存
- 一级缓存未命中,再查询数据库
- sqlsession关闭之后,一级缓存中的数据会写入到二级缓存中。
mybatis整合第三方EHCache
代替二级缓存,cache中的type指定缓存技术
<!--mybatis ehcache整合包-->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<!--slf4j日志门面的一个具体实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
mybatis的逆向工程
根据数据库表,由框架根据数据库表,反向生成实体类,mapper接口,mapper映射文件