1、在resources文件夹下新建mybatis-config.xml和db.properties,mybatis-config.xml引入db.properties中关于数据库的配置:
<properties resource="db.properties"/>
然后用<environments标签里面将对应的driver、url、username、password注入。
2、设置驼峰(例如:sql查询属性名:last_name -> 实体类的属性名:lastName)
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
3、为实体类起别名(自动转换大小写)
<typeAliases>
<!--第一种方式:-->
<!--<typeAlias type="com.atguigu.bean.Employee" alias="emp"/>
<typeAlias type="com.atguigu.bean.Department" alias="Department"/>-->
<!--第二种方式:省略alias属性,别名默认就是类名-->
<!--<typeAlias type="com.atguigu.bean.Employee"/>
<typeAlias type="com.atguigu.bean.Department"/>-->
<!--第三种方式 ★:指定包下的所有实体类,都会起别名为类名-->
<package name="com.atguigu.bean"/>
</typeAliases>
4、插件(以分页插件为例,先在pom.xml中导入相关依赖,然后就到mybatis-config.xml中进行配置)
<plugins>
<!--添加分页插件:自动将分页的sql语句,添加到sql语句中 interceptor:分页插件核心类的全类名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL 六种数据库-->
<property name="helperDialect" value="Mysql"/>
</plugin>
</plugins>
那么在测试类中:
//pageNum:从第几页开始 pageSize:一页多少条数据
Page page = PageHelper.startPage(2, 3);//直接存放到Page对象中,从第2页开始,每页3条数据
//测试page对象
//(1)当前是第几页
int pageNum = page.getPageNum();
//(2)一页有多少条数据
int pageSize = page.getPageSize();
//(3)总页数:一共有多少页
int pages = page.getPages();
//(4)总条数:符合查询条件的总条数
long total = page.getTotal();
//(5)当前页开始的行
int startRow = page.getStartRow();
//(6)当前页结束的行
int endRow = page.getEndRow();
//测试PageInfo对象
//创建PageInfo对象:将查询结果employees对象传入进行创建
PageInfo<Employee> pageInfo = new PageInfo(employees, 3);//navigatePages为3,意思是前端一次只展示3个导航页面:1,2,3或者2,3,4以此类推
//(1)直接显示查询列表
List<Employee> list = pageInfo.getList();//查询结果
System.out.println(list);//需要重写Employee类的toString方法
list.stream().forEach(System.out::println);//在页面端直接遍历拿结果
//(2)前一页的页码
int prePage = pageInfo.getPrePage();
//(3)下一页的页码
int nextPage = pageInfo.getNextPage();
//(4)是否有上一页
boolean hasPreviousPage = pageInfo.isHasPreviousPage();
//(5)是否有下一页
boolean hasNextPage = pageInfo.isHasNextPage();
//(6)是否是第一页
boolean isFirstPage = pageInfo.isIsFirstPage();
//(7)是否是最后一页
boolean isLastPage = pageInfo.isIsLastPage();
//(8)当前状态下导航页的页码
int[] navigatepageNums = pageInfo.getNavigatepageNums();
System.out.println("navigatepageNums = " + Arrays.toString(navigatepageNums));//Arrays.toString()将数组转换成字符串
5、加载映射文件
<mappers>
<!--方式一:单个文件进行加载-->
<!-- <mapper resource="EmployeeDao.xml"/>-->
<!--方式二:将指定包下的接口和映射文件进行加载
条件:持久层接口和映射文件->同包同名
-->
<package name="com.atguigu.dao"/>
</mappers>
常用方式二,但是要求接口和映射文件同包同名,例如,在com.atguigu.dao文件夹下新建EmployeeDao接口,我们写的EmployeeDao.xml就要放到相同包中,在resources文件夹下新建目录com/atguigu/dao,将EmployeeDao.xml放到这个dao文件夹下。
自动映射
1、增
如果想要获取/自动返回自增主键,在对应的xml文件中的sql语句前缀加上useGeneratedKeys=“true” keyProperty="id"两个属性(这里是将id作为自增主键)。
<!--int insert(Employee employee);-->
<!--useGeneratedKeys:是否自动返回自增主键
keyProperty:将自增的主键,设置到参数的哪个属性上(并不是以返回值的形式返回的),这里绑定的是id属性-->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into employees values(null,'${lastName}','${email}',${gender},${salary},${deptId})
</insert>
在控制类中的代码就这样写:
Employee employee=new Employee(null,"胖东来","pangdonglai@163.com",1,8888.0,2);
employeeDao.insert(employee);
System.out.println(employee.getId());//新增完后输出employee对象,可以直接用getId,getEmail等,因为xml中绑定了keyProperty="id"
2、删
<!--int delete(Integer id);-->
<delete id="delete">
delete from employees where id=#{id}
</delete>
3、改
<!--int update(Employee employee);-->
<update id="update">
update employees set
last_name=#{lastName},email=#{email},gender=#{gender},salary=#{salary},dept_id=#{deptId}
where id=#{id}
</update>
4、查
(1)传入一个参数时
<!--List<Employee> getByLastName(String name);-->
<select id="getByLastName" resultType="Employee">
select * from employees where last_name like concat('%',#{abc},'%')
</select>
(2)传入多个参数时
一般情况:
<!--List<Employee> getByNameAndSalary(String name,Double salary);
多个普通参数:将多个参数存放在一个Map集合内的value值上,key值是什么?[arg0,arg1,param1,param2]
param1~paramn
arg0~argn
第一个参数#{}里面可以写param1/arg0 第二个参数#{}里面可以写param2/arg1
-->
<select id="getByNameAndSalary" resultType="Employee">
select * from employees where last_name like concat('%',#{param1},'%') and salary>#{arg1}
</select>
有命名参数时,例如name属性需要赋给lastName:
<!-- List<Employee> getByNameAndSalary(@Param("lastName") String name, Double salary);
命名参数:变成了[lastName,arg1,param1,param2]
第一个参数#{}里面只能写param1/lastName 第二个参数#{}里面可以写param2/arg1
想一想:如果第二个salary也被命名为Salary呢?-这时变成了[lastName,Salary,param1,param2]
那么第一个参数#{}里面只能写param1/lastName 第二个参数#{}里面可以写param2/Salary
-->
<select id="getByNameAndSalary" resultType="Employee">
select * from employees where last_name like concat('%',#{lastName},'%') and salary>#{arg1}
</select>
(3)传入参数是javabean实体类
可以#{对象的属性名},它会直接调取实体类的get方法:
<!--List<Employee> getByEmployee(Employee employee); -->
<select id="getByEmployee" resultType="Employee">
select * from employees where last_name = #{lastName} and salary=#{salary}
</select>
(4)传入参数是Map
<!--List<Employee> getByMap(Map map);
#{map的key值}
-->
<select id="getByMap" resultType="Employee">
select * from employees where last_name = #{name} and salary=#{salary}
</select>
因为我们在MyBatisTest中是这么定义的:(传入的key分别是name和salary)
Map map=new HashMap();
map.put("name","胖东来666");
map.put("salary","88888");
List<Employee> emps = employeeDao.getByMap(map);
emps.stream().forEach(System.out::println);
(5)返回类型是Map
查询单行数据返回Map集合:
<!--Map<String,Object> getByIdReturnMap(Integer id);
数据库中字段名-key 数据库中字段值-value map是别名,它在typeAlias中已经被定义了
-->
<select id="getByIdReturnMap" resultType="map">
select * from employees where id=#{id}
</select>
那么我们在MyBatisTest中进行测试:
Map<String, Object> map = employeeDao.getByIdReturnMap(2);
System.out.println(map);//可以直接输出map,但是是无序的
查询多行数据返回Map集合:
在EmployeeDao接口中定义:用注解的方式指定id列作为key,其数据类型是Integer
@MapKey("id")
Map<Integer,Employee> getAllReturnMap();
在sql语句的xml中:
<!--@MapKey("id")
Map<Integer,Employee> getAllReturnMap();
返回的类型还是Employee,因为返回的value是Employee对象-->
<select id="getAllReturnMap" resultType="Employee">
select * from employees
</select>
我们在MyBatisTest中进行测试:
Map<Integer, Employee> allReturnMap = employeeDao.getAllReturnMap();
Set<Map.Entry<Integer, Employee>> entries = allReturnMap.entrySet();
for (Map.Entry<Integer, Employee> entry : entries) {
System.out.println( entry);
}
手动映射
1、需要手动映射的情况:
①查询结果的字段名和pojo的属性名不一致,且不满足自动驼峰
②如果出现多表查询,会有对象关联情况出现,使用手动映射比较方便
2、对于情况①
(1)创建resultMap标签(就是让开发者规定,sql语句的字段名对应pojo中的哪个属性)
id: 随意起名,唯一标识 type: 指定你要为哪个pojo做手动映射关系
在EmployeeDao.xml中:property表示映射在Employee实体类中属性名–column表示其在employees表中的列名
<resultMap id="empYingShe" type="Employee">
<!--主键-->
<id property="id" column="id"></id>
<!--非主键-->
<result property="name" column="last_name"></result>
<result property="email" column="email"></result>
<result property="gender" column="gender"></result>
<result property="salary" column="salary"></result>
<result property="deptId" column="dept_id"></result>
</resultMap>
(2)将select标签中的resultType属性换成resultMap属性,并且将resultMap标签的id属性值设置给resultMap属性
也就是说,其中的select语句的resultMap要改为empYingShe:
<select id="getById" resultMap="empYingShe">
select * from employees where id=#{id}
</select>
3、对于情况②
一对多:查询名叫“胖东来”的员工信息(包括职位信息),
sql语句应该为:
SELECT employees.*,departments.id did,departments.name
FROM employees
LEFT JOIN departments
ON employees.`dept_id`=departments.`id`
WHERE last_name=“胖东来”
方式1–两表联查
(1) 创建resultMap标签,id=“empMap01”
<resultMap id="empMap01" type="Employee01">
<!--对employees表的属性定义-->
<id property="id" column="id"></id>
<result property="name" column="last_name"></result>
<result property="email" column="email"></result>
<result property="gender" column="gender"></result>
<result property="salary" column="salary"></result>
<!--将sql语句中部门的数据,映射到employee01对象中的department对象内-->
<!--对一的关系:association
property:关联对象的属性名(我们在Employee01实体类中定义的Department类对象的名字)
javaType:关联对象的类型(使用的是别名,不然的话要写全类名)
-->
<association property="department" javaType="Department">
<!--(下面是对Department表中的两个属性进行定义)-->
<id property="id" column="did"></id>
<result property="name" column="name"></result>
</association>
</resultMap>
(2)再多表联查
<select id="getByName" resultMap="empMap01">
SELECT employees.*,departments.id did,departments.name FROM employees LEFT JOIN departments
ON employees.`dept_id`=departments.`id`
WHERE last_name=#{name}
</select>
方式2–分布查询
要求查询id=2的部门内的所有员工信息。
sql语句:第一次查询:select * from departments where id=2;
第二次查询:select * from employees where dept_id=2;
(1)在Department01Dao.xml中:
<resultMap id="deptMap02" type="Department01">
<!--departments表-->
<id property="id" column="dept_id"></id>
<result property="name" column="name"></result>
<!--对多的处理 -employees表
property:设置需要处理的集合属性名(在Department01实体类中定义的List<Employee> emps;名称为emps)
ofType:指定集合内泛型的类型(在Department01实体类中定义的List<Employee> emps;列表泛型是Employee)
select:调用方法,发送第二条sql语句(根据部门的id查找所有员工信息)
column:第一条sql语句中哪一列的值,传递到第二条sql语句中作为查询条件
-->
<collection property="emps" ofType="Employee"
select="com.atguigu.dao.EmployeeDao.getByDeptId"
column="id">
</collection>
</resultMap>
并写第一条sql语句:(用resultMap联系起来)
<select id="getById" resultMap="deptMap02">
SELECT * FROM departments WHERE id=#{id}
</select>
(2)然后到EmployeeDao.xml中:
<select id="getByDeptId" resultType="Employee">
SELECT * FROM employees WHERE dept_id=#{deptId}
</select>
拓展:在分布查询中,上面是将第一条sql语句中的第一列传到第二条sql语句作为查询条件,如果多列怎么办?
模拟:查询部门信息,将id这一列和name这一列的值传到第二条sql语句。
(1)在Department01Dao.xml中的collection标签修改:将id和name都传给getByDeptId方法。
<resultMap id="deptMap02" type="Department01">
<!--departments表-->
<id property="id" column="dept_id"></id>
<result property="name" column="name"></result>
<collection property="emps" ofType="Employee"
select="com.atguigu.dao.EmployeeDao.getByDeptId"
column="idkey=id,namekey=name">
</collection>
</resultMap>
并写第一条sql语句:(用resultMap联系起来)
<select id="getById" resultMap="deptMap02">
SELECT * FROM departments WHERE id=#{id}
</select>
(2)在EmployeeDao.xml中:
<select id="getByDeptId" resultType="Employee">
SELECT * FROM employees WHERE dept_id=#{idkey} and last_name=#{namekey}
</select>
1、where标签
if?标签
?????test属性:条件 ??原理:条件成立则拼接if内的sql语句,否则不拼接
where?标签
?????如果有if是成立的,会加where关键字,反之就不加
?????如果where后多一个and,会自动去除该语句最前面的and,如果and写在该语句的最后面,不会自动去除。
<!--List<Employee> getByMap(Map map);-->
<select id="getByMap" resultType="Employee">
select * from employees
<where>
<if test="name!=null and name!=''">
and last_name like concat('%',#{name},'%')
</if>
<if test="salary!=null and salary!=''">
and salary>#{salary}
</if>
<if test="deptId!=null and deptId!=''">
and dept_id=#{deptId}
</if>
</where>
</select>
在Employee01Test的testQuery01()方法中,将查询条件存入Map集合,然后调用getByMap()方法进行sql语句拼接:
Map<String, Object> map = new HashMap<>();
map.put("name","胖东来");//名字中含有"胖东来"的
map.put("salary",8888.0);//工资salary>8888.0的
map.put("deptId",2);//部门编号dept_id=2的
List<Employee> emps = employeeDao.getByMap(map);
emps.stream().forEach(System.out::println);
2、trim标签
????prefix 添加前缀 suffix 添加后缀
????prefixOverrides 去除前缀 suffixOverrides 去除后缀
??需求:如果有if成立,添加一个where的前缀(prefix=“where”),如果前面多一个and,去除前面的and(prefixOverrides=“and”)。
<select id="getByMap1" resultType="Employee">
select * from employees
<trim prefix="where" prefixOverrides="and">
<if test="name!=null and name!=''">
and last_name like concat('%',#{name},'%')
</if>
<if test="salary!=null and salary!=''">
and salary>#{salary}
</if>
<if test="deptId!=null and deptId!=''">
and dept_id=#{deptId}
</if>
</trim>
</select>
3、set标签
如果有if成立,则会添加set关键字(要保证set关键字必须添加)
会自动去除前后多出来的逗号
<!--int update(Employee employee);
需求:如果参数中的last_name和email为null的话,就不修改这一列的信息-->
<update id="update">
update employees
<set>
<!--如果last_name和email都为空会报错(因为set这个词没有了),所以保留这个id=id(因为id为主键)相当于执行无用操作,是为了保证set关键字必须加上-->
<if test="true">
id=#{id},<!--当然也可以换成1=1,只要保证保留set关键字就行-->
</if>
<if test="lastName!=null">
last_name=#{lastName},
</if>
<if test="email!=null">
email=#{email},
</if>
</set>
where id=#{id}
</update>
测试:
Employee employee = new Employee(1,null,"huahua@qq.com",1,2000.0,2);
employeeDao.update(employee);
4、choose选择结构
????when 可以有多个
????otherwise 只有一个
????原理:从上往下判断是否有成立的条件,如果有一个when成立(被用户发起该条件)则拼接sql语句,后面不走了,只拼接一条;否则往下执行,如果还是没有拼到,就拼otherwise里面的语句。
<select id="getByMap2" resultType="Employee">
select * from employees
<trim prefix="where" suffixOverrides="and">
<choose>
<when test="name!=null and name!=''">
last_name like concat('%',#{name},'%') and
</when>
<when test="salary!=null and salary!=''">
salary>#{salary} and
</when>
<when test="deptId!=null and deptId!=''">
dept_id=#{deptId}
</when>
<otherwise>
gender=1
</otherwise>
</choose>
</trim>
</select>
5、foreach:循环ids集合内的数据
??????collection:表明参数是什么(集合的话就是list,数组的话就是array)-这都是定义好了的别名
??????open:循环前置内容 close:循环后置内容
??????spearator:分割符??? item:变量名(自定义的)
??????index:自定义一个名字,通过这个名字可以获取到本次循环的索引
??应用位置:传入参数是集合或数组
<select id="getByIds" resultType="Employee">
select * from employees where id in
<foreach collection="list" open="(" close=")" separator="," item="id" index="i"><!--i=0,1,2,3;id=2,5,8,11-->
#{id}
</foreach>
</select>
测试–查找员工id为2,5,8,11的员工信息
List<Integer> ids = new ArrayList<>();
ids.add(2);
ids.add(5);
ids.add(8);
ids.add(11);
List<Employee> emps = employeeDao.getByIds(ids);
emps.stream().forEach(System.out::println);
6、sql标签:提取当前重复的sql片段,便于维护。
在EmployeeDao.xml中:将重复片段定义为“select01”。
<sql id="select01">
SELECT * FROM employees
</sql>
那么在需要使用该片段时就用<include refid=“select01”></include>替代该片段的代码:
<select id="getByDeptId" resultType="Employee">
<include refid="select01"></include>
WHERE dept_id=#{deptId}
</select>
7、补充:条件语句中and可以用&&代替。''可以用''代替。
<when test="name!=null and name!=''"> 等价于
<when test="name!=null && name!=''">
另外,Mybatis还有延迟加载、缓存机制和逆向工程(Mybatis03中介绍)。这里只做简单介绍:
(1)设置延迟加载只对day3中多表查询的分步查询有效:如果只使用员工信息employees表不使用部门信息department,那么只会发送第一条sql语句;
当用户需要使用departmrnt表的信息时,才会发送第二条sql语句。
(2)当再次发起相同的查询请求时,MyBatis不会再发送一次sql语句,会直接从缓存中调取上次的查询结果,即缓存。
(3)逆向工程可以快速根据数据库表生成对应的映射文件、接口类及bean类。支持基本的增删改查,以及QBC风格的条件查询。
在mybatis-config.xml中配置dao映射文件位置,运行即可生成对应main下的java文件夹下的com.atguigu.mbg文件夹及里面的bean类,dao类,以及resources文件夹下的com.atguigu.mbg.dao文件夹及里面的映射文件。