1.通过dao和映射文件的关联来完成操作---企业开发模式
思考: 我们之前使用SqlSession封装的一些方法可以完成crud操作,但是SqlSession封装的方法,传递的参数statement, 传递占位符的参数只能传递一个。而且他的方法名称都是固定。而真实在开发环境下我们不使用SqlSession封装的方法,而是习惯自己定义方法,自己调用自己的方法。
接口结合映射文件一起用。
1.1. 如何实现
(1)创建一个dao接口并定义自己需要的方法。
package com.wzh.dao;
import com.wzh.entity.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @ProjectName: mybatis0601_1
* @Package: com.wzh.dao
* @ClassName: UserDao
* @Author:
* @Description: 用户的操作接口
* @Date: 2022/6/1 18:40
* @Version: 1.0
*/
public interface UserDao {
/**
* 方法描述
* 查询所有
* @return:
*/
public List<User> findAll();
/**
* 方法描述
* 根据id查询单条记录
* @return:
*/
public User findOne(int id);
/**
* 方法描述
* 添加
* @return:
*/
public int insert(User user);
/**
* 方法描述
* 根据id删除
* @return:
*/
public int delete(int id);
/**
* 方法描述
* 修改
* @return:
*/
public int update(User user);
/**
* 方法描述
* 根据账号和密码查询用户
* @return:
* 使用@Param为参数起名 那么在映射文件中就可以使用该名称 不然要用param1 param2 ....
*/
public User findByNameAndPwd(@Param("username") String username,@Param("password") String password);
}
(2)创建映射文件
<?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">
<!--namespace:命名空间
可以随便起名,但是后期我们要求命名空间的值必须和所对应的dao相同
-->
<mapper namespace="com.wzh.dao.UserDao">
<!--useGeneratedKeys:设置使用生成的主键
keyProperty: 赋值给哪个属性 -->
<insert id="insert" parameterType="com.wzh.entity.User" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(null,#{username},#{password},#{relname})
</insert>
<delete id="delete">
delete from t_user where id=#{id}
</delete>
<update id="update">
update t_user set username=#{username},password=#{password},relname=#{relname} where id=#{id}
</update>
<!--查询 根据id查询用户信息
select标签用于查询的标签
id:标签的唯一标识
resultType: 定义返回的类型 把sql查询的结果封装到哪个实体类中
#{id}===表示占位符等价于? 这是mybatis框架的语法
-->
<select id="findAll" resultType="com.wzh.entity.User">
select * from t_user
</select>
<select id="findOne" resultType="com.wzh.entity.User">
select * from t_user where id=#{id}
</select>
<select id="findByNameAndPwd" resultType="com.wzh.entity.User">
<!--
根据账号和密码查询用户信息
mybatis默认会把多个参数封装成param1 param2 .......
这种方式名称不见名知意
如果想自己起名称使用@Param
-->
select * from t_user where username=#{username} and password=#{password}
</select>
</mapper>
注意: namespace必须和dao接口一样,而且标签的id必须和你接口的方法名一样。
<!--namespace必须和接口的全路径匹配-->
<!--id名称必须和接口中方法名一样-->
(3)测试
import com.wzh.dao.UserDao;
import com.wzh.entity.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.Reader;
import java.util.List;
/**
* @ProjectName: mybatis0601_1
* @Package: PACKAGE_NAME
* @ClassName: Test01
* @Author: 王振华
* @Description:
* @Date: 2022/6/1 18:57
* @Version: 1.0
*/
public class Test01 {
@Test
public void testFindAll() throws Exception{
Reader reader = Resources.getResourceAsReader("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = factory.openSession();
UserDao userDao = session.getMapper(UserDao.class); //获取相应接口的代理对象
List<User> list = userDao.findAll();
System.out.println(list);
session.commit();
session.close();
}
@Test
public void testFindOne() throws Exception{
Reader reader = Resources.getResourceAsReader("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = factory.openSession();
UserDao userDao = session.getMapper(UserDao.class);//获取相应接口的代理对象
User user = userDao.findOne(1);
System.out.println(user);
session.commit();
session.close();
}
@Test
public void testInsert() throws Exception{
Reader reader = Resources.getResourceAsReader("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = factory.openSession();
UserDao userDao = session.getMapper(UserDao.class);
User user = new User("wzh", "123456", "王振华");
System.out.println(user);
int row = userDao.insert(user);
System.out.println(user);
session.commit();
session.close();
}
@Test
public void testDelete() throws Exception{
Reader reader = Resources.getResourceAsReader("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = factory.openSession();
UserDao userDao = session.getMapper(UserDao.class);
int row = userDao.delete(84);
System.out.println(row);
session.commit();
session.close();
}
@Test
public void testUpdate() throws Exception{
Reader reader = Resources.getResourceAsReader("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = factory.openSession();
UserDao userDao = session.getMapper(UserDao.class);
int row = userDao.update(new User(76,"wzh", "123456", "王振华"));
System.out.println(row);
session.commit();
session.close();
}
@Test
public void testFindByNameAndPwd() throws Exception{
Reader reader = Resources.getResourceAsReader("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = factory.openSession();
UserDao userDao = session.getMapper(UserDao.class);
User user = userDao.findByNameAndPwd("wzh", "123456");
System.out.println(user);
session.commit();
session.close();
}
}
常见的bug
(1)namespace和接口名不对应。 xml文件没有注册到mybatis配置文件中
(2)映射文件中的标签id和接口中方法名不对应
1.2.安装mybatis插件
作用: 检查dao和映射文件是否匹配
二者有一即可
2.传递多个参数
我们在dao接口中某些方法可能需要传递多个参数,譬如: 登录(username,password)
我们需要在参数处使用@Param()为参数起名。
3.添加时如何返回递增的主键值。
需要返回添加数据库后的id值。
keyProperty必须时UserDao参数的主键 如果参数是Map 需要map.id
@Test
public void testInsert() throws Exception{
Reader reader = Resources.getResourceAsReader("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = factory.openSession();
UserDao userDao = session.getMapper(UserDao.class);
User user = new User("wzh", "123456", "王振华");
System.out.println(user); //id=null
int row = userDao.insert(user);
System.out.println(user); //id有值
session.commit();
session.close();
}
4.解决字段名与实体类属性名不相同的冲突
问题: 查询时返回一个null,或者某个列没有值
可以有两种解决办法:
第一种: 为查询的列起别名,而别名和属性名一致。
<mapper namespace="com.wzh.dao.StudentDao">
<!--查询所有-->
<select id="findAll" resultType="com.wzh.entity.Student">
select stu_id stuId,stu_name stuName,stu_sex stuSex,stu_age stuAge,stu_salary stuSalary from student
</select>
</mapper>
第二种: 使用resultMap完成列和属性之间的映射关系。
<mapper namespace="com.wzh.dao.StudentDao">
<!--resultType和ResultMap二者只能用一个-->
<select id="findAll" resultMap="stu">
select * from student
</select>
<resultMap id="stu" type="com.wzh.entity.Student">
<!--主键的映射关系 column:列名 property:属性名-->
<id column="stu_id" property="stuId"/>
<!--普通列的映射关系-->
<result column="stu_name" property="stuName"/>
<result column="stu_sex" property="stuSex"/>
<result column="stu_age" property="stuAge"/>
<result column="stu_salary" property="stuSalary"/>
</resultMap>
</mapper>
resultMap的id必须和select里的resultMap一致
如果列名和属性名有些一致的,可以在resultMap中不写映射关系,不需要全部都写,不一致的写就行
5. 动态sql
5.1什么是动态sql
根据参数的值,判断sql的条件。
name!=null address!=null
select * from 表名 where name=#{name} and address=#{address}
name==null
select * from 表名 where address=#{address}
5.2.为什么要使用动态sql
5.3.mybatis中动态sql标签有哪些
5.3.1.if标签--单条件判断
//如果stuAge不为null则按照stuAge查询 如果为null则查询所有 public interface StudentDao { public List<Student> findAll(); public List<Student> findByCondition(@Param("stuName") String stuName,@Param("stuAge") Integer stuAge,@Param("stuSalary") Double stuSalary); }
StudentMapper.xml:
<select id="findByCondition" resultMap="stu">
select * from student where 1=1
<!--符合条件执行 不符合跳过-->
<if test="stuName!=null and stuName!=''">
and stu_name=#{stuName}
</if>
<if test="stuAge!=null">
and stu_age=#{stuAge}
</if>
<if test="stuSalary!=null">
and stu_salary=#{stuSalary}
</if>
</select>
<resultMap id="stu" type="com.wzh.entity.Student">
<id column="stu_id" property="stuId"/>
<result column="stu_name" property="stuName"/>
<result column="stu_sex" property="stuSex"/>
<result column="stu_age" property="stuAge"/>
<result column="stu_salary" property="stuSalary"/>
</resultMap>
测试:
@Test
public void testFindCondition() throws Exception{
Reader reader = Resources.getResourceAsReader("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = factory.openSession();
StudentDao studentDao = session.getMapper(StudentDao.class);
List<Student> list = studentDao.findByCondition( null,null, null);
System.out.println(list);
session.commit();
session.close();
}
5.3.2. choose标签 多条件分支判断
choose就相当于多分支条件语句 类似于java中的switch...case...default
<mapper namespace="com.wzh.dao.StudentDao">
<select id="findByCondition" resultMap="stu">
select * from student where 1=1
<!--when中有一个条件成立就不会再往下执行,当所有条件都不成立时,就会执行otherwise中的语句-->
<choose>
<when test="stuName!=null and stuName!=''">
and stu_name=#{stuName}
</when>
<when test="stuAge!=null">
and stu_age=#{stuAge}
</when>
<when test="stuSalary!=null">
and stu_salay=#{stuSalary}
</when>
<otherwise>
and stu_sex=0
</otherwise>
</choose>
</select>
<resultMap id="stu" type="com.wzh.entity.Student">
<id column="stu_id" property="stuId"/>
<result column="stu_name" property="stuName"/>
<result column="stu_sex" property="stuSex"/>
<result column="stu_age" property="stuAge"/>
<result column="stu_salary" property="stuSalary"/>
</resultMap>
</mapper>
测试同上
5.3.3.where标签
我们观察到上面的sql都加了 where 1=1 ,如果不使用where 1=1 那么你的动态sql可能会出错。 我们能不能不加where 1=1呢! 可以 那么我们就可以使用where标签,作用:可以自动为你添加where关键字,并且可以帮你去除第一个and |or
<select id="findByCondition" resultMap="stu">
select * from student
<where>
<if test="stuName!=null and stuName!=''">
and stu_name=#{stuName}
</if>
<if test="stuAge!=null">
and stu_age=#{stuAge}
</if>
<if test="stuSalary!=null">
and stu_salary=#{stuSalary}
</if>
</where>
</select>
<resultMap id="stu" type="com.wzh.entity.Student">
<id column="stu_id" property="stuId"/>
<result column="stu_name" property="stuName"/>
<result column="stu_sex" property="stuSex"/>
<result column="stu_age" property="stuAge"/>
<result column="stu_salary" property="stuSalary"/>
</resultMap>
5.3.4.set标签
这个配合if标签一起用,一般用在修改语句。如果传递的参数值为null,那么应该不修改该列的值。
<!--修改-->
<update id="update">
update tb_emp
<!--set:可以帮我们生成关键字 set并且可以去除最后一个逗号-->
<set>
<if test="name!=null and name!=''">
name=#{name},
</if>
<if test="age!=null">
age=#{age},
</if>
<if test="salary!=null">
salary=#{salary}
</if>
where id=#{id}
</set>
</update>
这里我们以tb_emp表为例,属性有id,name,age,job,salary,entrydate,managerid,dept_id,我们只需要关注name,age,salary三列,测试代码如下:
@Test
public void testUpdate() throws Exception{
Reader reader = Resources.getResourceAsReader("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = factory.openSession();
EmpDao empDao = session.getMapper(EmpDao.class);
int row = empDao.update(new Emp(29, "孙悟空", null, 5000.00));
System.out.println(row);
session.commit();//必须提交
session.close();
}
因为我们的age属性为null,所有sql语句为:
5.3.5.foreach标签
循环标签,可以帮助我们处理一些动态的批量任务,例如批量添加,批量查询,批量删除等等。我们可以传入一个数组或者一个list集合,然后通过foreach标签将集合内的条件一个一个的放入我们的sql语句从而实现动态的批量处理效果。
foreach标签内属性:
collection:类型 如果你使用的为数组array 如果你使用的为集合 那么就用list
item:数组中每个元素赋值的变量名
open: 以谁开始
close:以谁结束
separator:分割符
这里以tb_emp表为例:
(1)根据多个id查询多条记录(批量查询)
EmpDao:
EmpMapper.xml :
<sql id="empMap">
id,name,age,job,salary,entrydate,managerid,dept_id
</sql>
<!--根据多个id查询多条记录-->
<select id="selectByIds" resultMap="eMap">
<!--
如果你使用的为数组array 如果你使用的为集合 那么就用list
collection:类型
item:数组中每个元素赋值的变量名
open: 以谁开始
close:以谁结束
separator:分割符
-->
select <include refid="empMap"/> from tb_emp where id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
<resultMap id="eMap" type="com.wzh.entity.Emp">
<result column="dept_id" property="deptId"/>
</resultMap>
(2)根据多个id删除多条数据(批量删除)
EmpDao:
EmpMapper.xml
<!--批量删除-->
<delete id="delete">
delete from tb_emp where id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
(3)批量添加学生信息
这里我们传入的参数用List<Emp>集合。集合里面装的都是Emp对象类型的数据。这样才能将要添加的学生的信息传入到sql中。
EmpDao:
EmpMapper.xml:
注意,我们要想调用list集合内的Student对象的属性,调用的写法应该为“Xxx(item名称).xxx(属性名)”
<insert id="insert">
insert into tb_emp values
<foreach collection="list" item="emp" separator=",">
(null,#{emp.name},#{emp.age},#{emp.job},null,null,null,null)
</foreach>
</insert>
5.3.6.trim标签
trim元素可以帮助我们去掉一下and、or等,prefix代表语句前缀, prefixOverrides代表要去掉的字符串
<select id="selByChoose" resultType="Account">
select id,name,created,updated,money from account
<trim prefix="where" prefixOverrides="and">
<choose>
<when test="name !=null and name !=''">
and name like concat('%',#{name},'%')
</when>
<when test="money !=null and money !=''">
and money =#{money}
</when>
<otherwise>
and isdeleted=1
</otherwise>
</choose>
</trim>
</select>
5.3.7.sql片段
在执行查询语句时不建议大家使用select *, 建议大家把查询的列写出。
定义sql片段,里面包含查询的列名
6.mybatis映射文件处理特殊字符
在Mapper中写我们的sql语句中,会遇到一些特殊的字符,他会与我们的sql代码会产生冲突,例如我们sql中写这样的语句:
可以看到我们的小于号,就是一种特殊字符。那么我们该如何处理这种问题?
有两种方法可以解决。
第一种:使用转义符。java中有这些特殊字符的转义符。
第二种:用 <![CDATA[sql语句]]> 方法 注意这种方法不能括住我们的标签(例如include标签),我们可以在需要用到的地方使用这个方法,没必要整个sql语句都套入在内。
7.mybatis完成模糊查询
模糊查询的sql语句:
select * from 表名 where 列名 like '%a%'
(1)使用字符串函数 完成拼接 -----concat(这属于sql基础的知识点)
<select id="selectByName" resultMap="eMap">
select <include refid="emp"/> from tb_emp where name like concat('%',#{name},'%')
</select>
(2) 使用${ } 代替。 这种方式实际上是字符串的拼接,他并不能防止sql注入问题,而我们之前用的#{ }就相当于预编译,可以防止sql注入问题,#{}实际使用的PreparedStatement。所有这种方式不推荐使用,所谓防君子不防小人。
<select id="selectByName" resultMap="eMap">
select <include refid="emp"/> from tb_emp where name like '%${name}%'
</select>
8.联表查询
我们上边的操作,都是单表操作。实际开发中也会碰到联表操作。
8.1.一对一
例子:根据id查询员工信息并包括该员工所在的部门信息。
需要用的表:我们需要从多的一方来查询一的一方。这里员工表就相当于多的一方,部门表就相当于1的一方。
sql语句:
select * from tb_emp e join tb_dept d on e.dept_id=d.id where e.id=×××
实现方式一:
首先在我们的Emp实体类中(相当于多的一方)加入我们Dept类型(一的一方)的属性
package com.wzh.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @ProjectName: mybatis0602_2
* @Package: com.wzh.entity
* @ClassName: Student
* @Author: 王振华
* @Description:
* @Date: 2022/6/2 16:32
* @Version: 1.0
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp {
private Integer id;
private String name;
private Integer age;
private String job;
private Double salary;
private Date entrydate;
private Integer managerid;
private Integer deptId;
private Dept dept; //Dept类型的属性
}
package com.wzh.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @ProjectName: mybatis0602_2
* @Package: com.wzh.entity
* @ClassName: Dept
* @Author: 王振华
* @Description:
* @Date: 2022/6/2 16:34
* @Version: 1.0
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dept {
private Integer id;
private String name;
}
EmpDao:
EmpMapper.xml:
重点:resultMap标签内的association标签 :表示 一 的一方。
property:表示属性名(就是我们实体类种的aclass属性)。
javaType:表示该属性名对应的数据类型。
<sql id="empAndDept">
e.id,e.name,e.age,e.job,e.salary,e.entrydate,e.managerid,e.dept_id,d.id did,d.name dname
</sql>
<!--autoMapping 自动映射-->
<resultMap id="empMap" type="com.wzh.entity.Emp">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="job" property="job"/>
<result column="salary" property="salary"/>
<result column="entrydate" property="entrydate"/>
<result column="managerid" property="managerid"/>
<result column="dept_id" property="deptId"/>
<!--association: 表示一的一方
property: 它表示属性名
javaType: 该属性名对应的数据类型
-->
<association property="dept" javaType="com.wzh.entity.Dept">
<id column="did" property="id"/>
<result column="dname" property="name"/>
</association>
</resultMap>
<select id="findById" resultMap="empMap">
select <include refid="empAndDept"/> from tb_emp e join tb_dept d on e.dept_id=d.id where e.id=#{id}
</select>
实现方式二:
map封装(不推荐这种方式)。用这种方式封装我们查询到的数据,就不需要改动我们的实体类,也不用关心其属性内的映射关系。
//根据员工编号查询员工信息以及部门信息
public Map findById(Integer id);
EmpMapping.xml:
<!--key:value-->
<select id="findById" resultType="java.util.Map">
select <include refid="empAndDept"/> from tb_emp e join tb_dept d on e.dept_id = d.id where e.id=#{id}
</select>
需要避免的问题:两个表的列名应该尽量避免相同,如果相同,需要起别名来区别。resultMap中的标签内的column属性应该与你起了别名后的列名相同。Map本身也是无序不重复的,我们的属性名也就是map中的key值也需要保证不能相同。
sql片段里面是可以起别名的,不过要与sql语句的别名一致.
8.2.一对多
总结
1. 实际开发创建一个dao接口和映射文件。---namespace要和dao接口相同,id要和方法名相同。
2. 传递多个参数----@Param("名称") == xml #{名称}
3. 添加时需要获取递增的主键id.
4. 列名和属性名不一致。---别名 ===resultMap
5. 动态sql.
if
choose
whereset
foreach
sql片段
trim
6.mybatis映射文件处理特殊字符有两种解决方法:(1)转义字符 (2)<![CDATA[SQL语句]]>
7.mybatis完成模糊查询 (1)拼接字符串concat('%',#{内容},'%') (2)${} 不能防止sql注入
8.联表查询 (1)在一的实体类里面添加多的对象属性 (2)map封装 不推荐