1.能做什么
- MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射
- MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
- MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJO映射成数据库中的记录
2.mybatis的优点和缺点
1.sql语句与代码分离,存放于xml配置文件中:
优点:便于维护管理,不用在java代码中找这些语句;
缺点: JDBC方式可以用打断点的方式调试,但是Mybatis不能,需要通过log4j日志输出日志信息 帮助调试,然后在配置文件中修改。
2.用逻辑标签控制动态SQL的拼接:
优点:用标签代替编写逻辑代码;
缺点:拼接复杂SQL语句时,没有代码灵活,拼写比较复杂。不要使用变通的手段来应对这种复杂 的语句。
3.查询的结果集与java对象自动映射:
优点:保证名称相同,配置好映射关系即可自动映射,或者不配置映射关系,通过配置列名=字段名也可完成自动映射。
缺点:对开发人员所写的SQL依赖很强。
4.编写原生SQL:
优点:接近JDBC,比较灵活。
缺点:对SQL语句依赖程度很高;并且属于半自动,数据库移植比较麻烦,比如mysql数据库编程 Oracle数据库,部分的sql语句需要调整。
5.最重要的一点,使用的人多!公司需要!但是应为用了反射,效率会下降,所有有些公司会使用原生的jdbc
3.持久化&持久层
持久化
所谓持久化就是把数据存在磁盘而不是内存,我们的程序在运行时说的持久化通常就是指将内存的数据存在硬盘的过程
- 程序产生的数据首先都是在内存中;
- 内存是不可靠的,断电即失;
- 可靠的地方是:硬盘 U盘 光盘等;
高级一点:持久化就是将数据从瞬时状态转换为持久状态,长期保存
持久层
- 业务是需要操作数据的;
- 数据是存在磁盘的;
- 具体业务调用具体的数据库操作,耦合度太高,复用性太差;
- 将操作数据库的代码统一抽离出来,自然就形成了介于业务层和数据库中间的独立的层(即Dao层);
4.MyBatis核心配置文件(mybatis-config.xml)的相关信息
小知识:
- DTD(Document Type Definition)即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制 ;
- 一个DTD文档包含:(1)元素的定义规则;(2)元素间关系的定义规则;(3)元素可使用的属性,可使用的实体或符号规则;
- 当然我们还有可能接触其他类型的头文件,比如XML Schemal语言就是XSD,XML Schema描述了XML 文档的结构,可以用一个指定的XML Schema来验证某个XML。
- XML Schema基于XML,没有专门的语法
- XML Schema可以象其他XML文件一样解析和处理
- XML Schema比DTD提供了更丰富的数据类型.
- XML Schema提供可扩充的数据模型。
- XML Schema支持综合命名空间
- 总之不管是dtd还是xsd文件都是用来约束我们的xml文件,保证我们的xml能够使用哪些标签,保证xml 的有效性,在idea中工具还能根据头文件为我们提供强大的提示功能(说白了就是约束和提示的)。
5.lombok的使用
- @AllArgsConstructor:生成全参构造器。
- @NoArgsConstructor:生成无参构造器。
- @Getter/@Setter: 作用类上,生成所有成员变量的getter/setter方法;作用于成员变量上,生成该 成员变量的getter/setter方法。可以设定访问权限及是否懒加载等。
- @Data:作用于类上,是个符合注解:@ToString @EqualsAndHashCode @Getter @Setter @RequiredArgsConstructor
- @Log:作用于类上,生成日志变量。针对不同的日志实现产品,有不同的注解。
6.注解
@Param
是给参数命名,参数命名后就能根据名字得到参数值,正确的将参数传入sql语句中(一般通过#{}的方式,${}会有sql注入的问题)
例子:
mappe.java
Public User selectUser(@param("xxx") String name, @param("yyy") String password);
mapper.xml
<select id=" selectUser" resultMap="BaseResultMap">
select * from user
where user_name = #{xxx} and user_password=#{yyy}
</select>
总结:一个参数无所谓,多个参数要加@param;只是将形参转化成实参的过程,参数名并没啥实际意义,只是转换成了引用地址而已;
当你使用了使用@Param注解来声明参数时,如果使用 #{} 或 ${} 的方式都可以,当你不使用@Param注解来声明参数时,必须使用使用 #
{}方式,如果使用 ${} 的方式,会报错
7.模糊查询%的拼接
方案一:在Java代码中拼接
string name = "%IT%";
list<name> names = mapper.getUserByName(name);
<select id="getUsersByName">
select * from user where name like #{name}
</select>
方案二:在配置文件中拼接
string name = "IT";
list<name> names = mapper.getUserByName(name);
<select id="getUsersByName">
select * from user where name like '%${name}%';
</select>
8.map的使用
map可以代替任何的实体类,所以当我们数据比较复杂时,可以适当考虑使用map来完成相关工 作
1.写sql
<select id="getUsersByParams" parameterType="java.util.HashMapmap">
select id,username,password from user where username = #{name}
</select>
2.写方法
/**
* 根据一些参数查询
* @param map
* @return
*/
List<User> getUsersByParams(Map<String,String> map);
3.测试类
@Test
public void findByParams() {
UserMapper mapper = session.getMapper(UserMapper.class);
Map<String,String> map = new HashMap<String, String>();
map.put("name","白招文");
List<User> users = mapper.getUsersByParams(map);
for (User user: users){
System.out.println(user.getUsername());
}
}
总结:map工作中及其常用,特别在多表查询中,字段又多又复杂。
9.别名设置
1.例子:
<select id="getUsersByParams" parameterType="java.util.HashMap">
select id,username,password from user where username = #{name}
</select>
resultType写成java.util.HashMap,也行写成map也行
说明mybatis内置很多别名
Alias | Mapped Type |
---|---|
_long | long |
_int | int |
_integer | int |
_double | double |
_boolean | boolean |
string | String |
boolean | Boolean |
date | Date |
object | Object |
boolean | Boolean |
date | Date |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | Arraylist |
collection | Collection |
2.给自己类设置别名
在核心配置文件中加入
<typeAliases>
<typeAlias type="com.xinzhi.entity.User" alias="user"/>
</typeAliases>
标签中 有 type 和 alias 两个属性, type 填写 实体类的全类名, alias 可以不填,不填的话,默认是类名,不区分大小写, alias 填了的话就以 alias里的值为准
<typeAiases>
<package name="com.xinzhi.entity"/>
</typeAliases>
标签 为某个包下的所有类起别名; name 属性填写包名。 别名默认是类名,不区分大小 写。
@Alias` 注解 加在实体类上,为某个类起别名;例:`@Alias("User")
10.resultMap详解
数据库不可能永远是你所想或所需的那个样子(比如数据库中使用’_'命名,java中使用驼峰名字),属性名和字段名不一致,我们一般都会按照约定去设计数据
方案一:为列名指定别名 , 别名和java实体类的属性名一致:
<select id="selectUserById" resultType="User">
select id , username as name ,password from user where id = #{id}
</select>
备注:username是sql中的名字,name为java实体类中的名字
方案二:使用结果集映射->ResultMap 【推荐】:
<resultMap id="UserMap" type="User">
<!-- id为主键 -->
<id column="id" property="id"/>
<!-- column是数据库表的列名 , property是对应实体类的属性名 -->
<result column="username" property="name"/>
<result column="password" property="password"/>
</resultMap>
<select id="selectUserById" resultMap="UserMap">
select id , username , password from user where id = #{id}
</select>
方案三:下划线和驼峰命名的自动转化
核心配置文件中
<settings>
<!--开启驼峰命名规则-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
11.动态sql
动态SQL的元素:
元素 | 作用 | 备注 |
---|---|---|
if | 判断语句 | 单条件分支判断 |
choose、when、otherwise | 相当于Java中的 case when语句 | 多条件分支判断 |
trim、where、set | 辅助元素 | 用于处理一些SQL拼装问题 |
foreach | 循环语句 | 在in语句等列举条件常用 |
1.查询常用案例:
<select id="findUserById" resultType="com.xinzhi.entity.User">
select id,username,password from user
where 1 =1
<if test="id != null and id != ''">
AND id = #{id}
</if>
<if test="username != null and username != ''">
AND username = #{username}
</if>
<if test="password != null and password != ''">
AND password = #{password}
</if>
</select>
注:where 1 =1是为了去除后面的and字符串,除此之外,也可以使用,如下
开发当中常用
<select id="findUserById" resultType="com.xinzhi.entity.User">
select id,username,password from user
<where>
<if test="id != null and id != ''">
AND id = #{id}
</if>
<if test="username != null and username != ''">
AND username = #{username}
</if>
<if test="password != null and password != ''">
AND password = #{password}
</if>
</where>
</select>
2.查询辅助案例
注:下面的所有动态介绍可以当做技巧来用,功能和if一样,
choose、when、otherwise元素
<!-- 有name的时候使用name搜索,没有的时候使用id搜索 -->
<select id="select" resultType="com.xinzhi.entity.User">
SELECT * FROM user
<where>
<choose>
<when test="name != null and name != ''">
AND username LIKE concat('%', #{username}, '%')
</when>
<when test="id != null">
AND id = #{id}
</when>
</choose>
</where>
</select>
trim元素
trim元素意味着 我们需要去掉一些特殊的字符串,prefix代表的是语句的前缀,而prefixOverrides代表的是你需要去掉 的那种字符串,suffix表示语句的后缀,suffixOverrides代表去掉的后缀字符串。
<select id="select" resultType="com.xinzhi.entity.User">
SELECT * FROM user
<trim prefix="WHERE" prefixOverrides="AND">
<if test="username != null and username != ''">
AND username LIKE concat('%', #{username}, '%')
</if>
<if test="id != null">
AND id = #{id}
</if>
</trim>
</select>
3.常用修改案例
在update语句中,如果我们只想更新某几个字段的值,这个时候可以使用set元素配合if元素来完成。
注 意:set元素遇到,会自动把,去掉;id是必传选项
<update id="update">
UPDATE user
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="password != null and password != ''">
password = #{password}
</if>
</set>
WHERE id = #{id}
</update>
4.foreach元素(常用于批量语句中)
foreach元素是一个循环语句,它的作用是遍历集合,可以支持数组、List、Set接口。可以用于批量删除,查询,添加语句
<select id="select" resultType="com.xinzhi.entity.User">
SELECT * FROM user
WHERE id IN
<foreach collection="ids" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</select>
collection配置的是传递进来的参数名称(mapper中需要加别名); item配置的是循环中当前的元素; index配置的是当前元素在集合的位置下标; open和 close配置的是以什么符号将这些集合元素包装起来(open表示以什么开始,close表示以什么结束);separator是各个元素的间隔符。
5.sql片段
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽 取出来,然后使用时直接调用。
提取SQL片段:
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
引用SQL片段:
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace-->
<include refid="if-title-author"></include>
<!-- 在这里还可以引用其他的 sql 片段 -->
</where>
</select>
10.数据关系处理
部门和员工的关系,一个部门多个员工,一个员工属于一个部门
那我们可以采取两种方式来维护关系,一种在一的一方,一种在多的一方!
1.数据库设计:
CREATE TABLE `dept` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
)
CREATE TABLE `employee` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`did` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_did` FOREIGN KEY (`did`) REFERENCES `dept` (`id`)
)
方案一:在多的一方维护关系(结果进行嵌套处理)常用
实体类:
@Data
public class Dept implements Serializable{
private int id;
private String name;
}
@Data
public class Employee implements Serializable {
private int id;
private String name;
//维护关系
private Dept dept;
}
写方法
List<Employee> findAllEmployees();
mapper处理
<resultMap id="EmployeeDept" type="com.xinzhi.entity.Employee">
<id property="id" column="eid"/>
<result property="name" column="ename"/>
<!--property为关联对象在Student实体类中的属性,javaType为该属性的类型-->
<association property="dept" javaType="com.xinzhi.entity.Dept">
<!--column为数据的列表名,但是这里在下面的查询中修改了别名;property为实体类的名字-->
<id property="id" column="did"/>
<result property="name" column="dname"/>
</association>
</resultMap>
<select id="findAllEmployees" resultMap="EmployeeDept" >
select e.id eid,e.name ename, d.id did,d.name dname
from employee e left join dept d
on d.id = e.did
</select>
测试:
@Test
public void testFindAllEmployees() {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
List<Employee> allEmployees = mapper.findAllEmployees();
for (Employee allEmployee : allEmployees) {
System.out.println(allEmployee);
}
}
方案二:在一的一方维护关系(结果嵌套处理) 常用
实体类:
@Data
public class Dept {
private int id;
private String name;
//用于关系维护
List<Employee> employees;
}
@Data
public class Employee {
private int id;
private String name;
}
写方法:
Dept findDeptById(int id);
写配置文件
<resultMap id="DeptEmployee" type="com.xinzhi.entity.Dept">
<id property="id" column="did"/>
<result property="name" column="dname"/>
<collection property="employees" ofType="com.xinzhi.entity.Employee">
<result property="id" column="eid" />
<result property="name" column="ename" />
</collection>
</resultMap>
<select id="findDeptById" resultMap="DeptEmployee">
select e.id eid,e.name ename, d.id did,d.name dname
from dept d left join employee e
on d.id = e.did where e.id=#{id}
</select>
测试:
@Test
public void testFindDeptById() {
DeptMapper mapper = session.getMapper(DeptMapper.class);
Dept dept = mapper.findDeptById(1);
System.out.println(dept);
}
总结:Association和Collection使用时机和区别:
1.Association 在一对一,多对一时使用(在xx对一的时候)
2.Collection 在一对多,多对多时使用(在xx对多的时候)
11.Mybatis缓存
MyBatis提供一级缓存和二级缓存的机制。
一级缓存
一级缓存是sqlsession级别的缓存 ,在操作数据库时,需要构造sqlsession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据,不同的sqlsession之间的缓存区域是互相不影响的。mybatis默认支持并开启一级缓存
一级缓存工作原理:
- 第一次发起查询sql查询用户id为1的用户,先去找缓存中是否有id为1的用户,如果没有,再去数据 库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
- 如果sqlsession执行了commit操作(插入,更新,删除),会清空sqlsession中的一级缓存,避免 脏读
- 第二次发起查询id为1的用户,缓存中如果找到了,直接从缓存中获取用户信息
一级缓存如何失效:
- sqlSession不同
- 当sqlSession对象相同的时候,查询的条件不同,原因是第一次查询时候一级缓存中没有第二次 查询所需要的数据
- 当sqlSession对象相同,两次查询之间进行了插入的操作
- 当sqlSession对象相同,手动清除了一级缓存中的数据
注意:一级缓存必须配置日志功能,否则看不见
二级缓存
二级缓存是mapper级别的缓存
总结:
1.二级缓存产生与多个会话之间可以共享的数据(使用有代价,更新数据库时要及时更新缓存,否则有错误数据),
2.一级缓存提供当前会话共享的数据,
3.一般开发中只使用一级缓存,企业中要想使用二级缓存常用第三方缓存工具,如redis
12.小知识:Mybatis 插入获取主键方式
使用场景:一般我们添加数据不添加id,但是我们又要拿到id(正常逻辑是,先插入数据,在通过数据拿到id)
第一种:使用xml
- 直接在标签属性上添加 useGeneratedKeys(是否是自增长,必须设置 true) 和 keyProperty(实体类主键属性名称) 、keyColumn(数据库主键字段名称)
<insert id="insertOne" parameterType="com.dao.model.User" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
INSERT INTO user(id,name,age) VALUES(#{id},#{name},#{age})
</insert>
第二种:注解的方式
useGeneratedKeys(是否是自增长,必须设置 true) 和 keyProperty(实体类主键属性名称) 、keyColumn(数据库主键字段名称)
@Insert("INSERT INTO user(name,age) VALUES(#{user.name},#{user.age})")
@Options(useGeneratedKeys=true, keyProperty="user.id",keyColumn="id" )
public int insertOne(@Param("user")User user);
注意:在映射的 mapper 里面参数取了别名,所有的实体类的属性都要加上别名.属性名,比如注解方式的案例
第三种:全局设置(不推荐,一般哪里用就在哪里写)
mybatis-config.xml文件
<setting name="useGeneratedKeys" value="true"/>