下面显示的是元素的一些属性:
一、update元素和delete元素
- 首先在映射接口中添加用于更新和删除的方法
//更新方法 public boolean updateEmp(Employee emp); //删除方法 public boolean deleteEmpById(Integer id);
- 然后在映射器文件中添加对应的sql配置信息
<!-- 参数类型可以省略 --> <update id="updateEmp" parameterType="com.atguigu.mybatis.bean.Employee"> update tbl_employee set last_name=#{lastName},gender=#{gender},email=#{email} where id=#{id} </update> <delete id="deleteEmpById"> delete from tbl_employee where id=#{id} </delete>
- 进行测试
@Test public void test02() throws IOException { //1.根据MyBatis的配置文件,即mybatis-config.xml创建SqlSessionFactory SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); //2.获取session实例,能直接执行*已经映射的SQL语句* SqlSession session = sqlSessionFactory.openSession(false);//设置默认不提交 try { //3.获取接口的实现类对象 EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); //4.测试删除方法 boolean isDelete = employeeMapper.deleteEmpById(3);//输出true或者false System.out.println(isDelete); //5.手动提交 session.commit(); } finally { //4.关闭会话session session.close(); } }
二、insert元素-插入语句
简单应用
- 首先在映射接口中添加用于插入的方法
//增加方法 public void addEmp(Employee emp);
- 然后在映射器文件中添加对应的sql配置信息
<insert id="addEmp"> insert into tbl_employee(last_name,gender,email) values(#{lastName},#{gender},#{email}) </insert>
- 测试代码
@Test public void test02() throws IOException { //1.根据MyBatis的配置文件,即mybatis-config.xml创建SqlSessionFactory SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); //2.获取session实例,能直接执行*已经映射的SQL语句* SqlSession session = sqlSessionFactory.openSession(false);//设置默认不提交 try { //3.获取接口的实现类对象 EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); //4.测试添加 Employee emp = new Employee(null, "张三", "1", "[email protected]"); employeeMapper.addEmp(emp); //5.手动提交 session.commit(); } finally { //4.关闭会话session session.close(); } }
获取自增主键的值
MySQL支持自增主键,自增主键值的获取,MyBatis也是利用statement.getGenreatedKey();
MyBatis使用insert元素的useGenreatedKeys="true"来使用自增主键获取主键值策略;
使用insert元素的keyProperty来指定对应的主键属性,也就是MyBatis获取到主键值以后,将这个值封装给javaBean的对应属性。
- 修改映射器文件中的insert元素
<insert id="addEmp" useGeneratedKeys="true" keyProperty="id"> insert into tbl_employee(last_name,gender,email) values(#{lastName},#{gender},#{email}) </insert>
- 测试代码
@Test public void test02() throws IOException { //1.根据MyBatis的配置文件,即mybatis-config.xml创建SqlSessionFactory SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); //2.获取session实例,能直接执行*已经映射的SQL语句* SqlSession session = sqlSessionFactory.openSession(false);//设置默认不提交 try { //3.获取接口的实现类对象 EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); //4.测试添加 Employee emp = new Employee(null, "张三", "1", "[email protected]"); employeeMapper.addEmp(emp); //输出员工的id System.out.println(emp.getId()); //5.手动提交 session.commit(); } finally { //4.关闭会话session session.close(); } }
三、select元素-查询语句
参数处理
单个参数
比如我要根据employee的id值来查询对应的员工,这时候对应的查询方法的参数只有一个,就是id值。在映射器中的对应SQL语句中,#{参数名}中的参数名MyBatis不做特殊处理,也就是该参数名可以随便取名。
多个参数
1. 使用注解传递多个参数
- 首先在映射接口中定义如下方法
//根据员工id和姓名查询员工 public Employee gerEmpByIdAndName(Integer id, String lastName);
- 然后在映射器文件中配置如下SQL
<select id="gerEmpByIdAndName" resultType="com.atguigu.mybatis.bean.Employee"> select id,last_name,gender,email from tbl_employee where id=#{id} and last_name=#{lastName} </select>
- 运行测试之后会报如下错误
Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [0, 1, param1, param2]
MyBatis在处理传入的多个参数时,会将多个参数封装成一个Map集合,对应的键值为:
key:param1…paramN,或者参数的索引0…N
value:我们传入的参数值
#{}就是从map中获取指定的key值,所以我们应该传入key值而不是value值。
解决:
使用命名参数,即明确指定封装参数时map的key,封装后的map集合的key值为使用@Param注解指定的值,value为参数值。
修改映射接口,指定map集合的键即可:
//根据员工id和姓名查询员工
public Employee gerEmpByIdAndName(@Param("id")Integer id, @Param("lastName")String lastName);
2. 通过POJO传递多个参数
- 存在一个POJO-Employee
- 此时在映射接口定义如下方法:
//根据POJO查询员工 public Employee getEmpByBean(Employee emp);
- 在映射器配置如下SQL语句,这里是通过传入的Java Bean的id和lastName来查询。
<select id="getEmpByBean" resultType="com.atguigu.mybatis.bean.Employee"> select id,last_name,gender,email from tbl_employee where id=#{id} and last_name=#{lastName} </select>
- 测试代码
3. 通过Map集合传递多个参数
- 在映射接口定义如下方法
//根据Map集合来查询员工 public Employee getEmpByMap(Map<String, Object> map);
- 在映射器中配置SQL信息
<select id="getEmpByMap" resultType="com.atguigu.mybatis.bean.Employee"> select id,last_name,gender,email from tbl_employee where id=#{id} and last_name=#{lastName} </select>
- 测试代码
4. 混合使用
注意:集合和数组都有默认的key。
#{}与${}的异同
相同点:两者都可以获取map中的值或者pojo对象属性的值。
不同点:
- #{}是以预编译的形式,将参数设置到sql语句中,类似于PreparedStatement,它可以防止sql注入。
- ${}是直接将取出的值拼接在sql语句中,类似于Statement,可能引发安全问题。
下面演示一下:
- 映射器文件的sql配置如下:
<select id="getEmpByMap" resultType="com.atguigu.mybatis.bean.Employee"> select id,last_name,gender,email from tbl_employee where id=#{id} and last_name=#{lastName} </select>
- 测试代码
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); //4.测试查询方法 Map<String,Object> map = new HashMap<>(); map.put("id", "1"); map.put("lastName", "Tom"); Employee emp = employeeMapper.getEmpByMap(map); System.out.println(emp);
- 运行结果
从输出的sql语句可以看出占位符?是存在的。
下面我们修改一下映射器文件的配置信息,更改为id=${id}:
<select id="getEmpByMap" resultType="com.atguigu.mybatis.bean.Employee">
select id,last_name,gender,email from tbl_employee where id=${id} and last_name=#{lastName}
</select>
再次测试的输出结果为:
${}的更多用法
比如我们需要按照年份进行分表拆分,然后使用传入的年份生成对应的表名,如
select * from ${year}_salary where xxx;
又比如我们通过字段名来对表的字段进行排序,那么我们也可以通过$来实现。如
select * from tbl_employee order by ${last_name} ${order}
显然,原生JDBC不支持占位符的地方我们就可以使用${}来进行取值。
#{}的更多用法
#{}还可以规定参数的一些规则,比如javaType、 jdbcType、 mode、 numericScale、
resultMap、 typeHandler、 jdbcTypeName。
下面来讲一下jdbcType的用法。
在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理(MyBatis默认将null设置为OTHER类型),比如Oracle数据库就无法识别OTHER类型。
我们可以在某个插入为null的数据来使用jdbcType设置。比如我要进入如下数据库操作:
<insert id="addEmp" useGeneratedKeys="true" keyProperty="id">
insert into tbl_employee(last_name,gender,email) values(#{lastName},#{gender},#{email})
</insert>
但是我在传入数据的时候,email的值是null,那么Oracle数据库是无法识别而报错的。我们可以更改为如下语句则可以解决这个问题:
上面的配置只使用单条sql语句。我们可以在全局配置文件设置settings来实现全局配置,即:
查询返回数据封装
List封装查询到的数据
- 在映射接口编写查询方法
//使用List封装查询到的数据 public List<Employee> getEmpsByLastNameLike(String lastName);
- 在映射器文件中编写指定的SQL配置,注意:返回类型是Employee对象
<select id="getEmpsByLastNameLike" resultType="com.atguigu.mybatis.bean.Employee"> select id,last_name,gender,email from tbl_employee where last_name like #{lastName} </select>
- 测试代码
@Test public void test04() throws IOException { //1.根据MyBatis的配置文件,即mybatis-config.xml创建SqlSessionFactory SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); //2.获取session实例,能直接执行*已经映射的SQL语句* SqlSession session = sqlSessionFactory.openSession(false);//设置默认不提交 try { //3.获取接口的实现类对象 EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); //4.查询 List<Employee> empsList = employeeMapper.getEmpsByLastNameLike("%张%"); for (Employee employee : empsList) { System.out.println(employee); } //5.手动提交 session.commit(); } finally { //4.关闭会话session session.close(); } }
Map封装查询到的数据
需求1:将Employee中的属性封装到一个Map中,其中key为Employee中的属性名,value为对应的值。
- 编写映射接口方法
//使用Map封装查询到的数据 public Map<String, Object> getEmpsByIdReturnMap(Integer id);
- 在映射器文件编写对应的SQL配置
<!-- 返回类型是Map集合,map是系统自定义的别名 --> <select id="getEmpsByIdReturnMap" resultType="map"> select * from tbl_employee where id=#{id} </select>
- 测试代码
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); //4.查询 Map<String, Object> empMap = employeeMapper.getEmpsByIdReturnMap(1); System.out.println(empMap);
- 运行结果
{gender=1, last_name=Tom, id=1, email=shotozheng@gmail.com}
需求2:将多个Employee对象封装到Map集合中,其中key为Employee中的id属性值,value为Employee对象。
- 同样需要在映射接口编写方法
注意:MapKey在于指定返回的Map集合的key为Employee的属性id@MapKey("id") public Map<Integer,Employee> getEmpsByLastNameLikeReturnMap(String lastName);
- 在映射器接口编写SQL配置
<!-- 注意:这里的返回类型是Employee对象 --> <select id="getEmpsByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee"> select * from tbl_employee where last_name like #{lastName} </select>
- 运行测试即可,不再赘述。
四、resultMap元素
比如我们在定义Java Bean时,POJO的属性名若与数据库表的字段名不一样则会导致查询出来的数据为null。我们之前是用通过给SQL的的字段命和POJO对应属性一样的名称或者通过驼峰命名法来处理这个问题。但是这些可能会导致SQL语句不好理解或者POJO中的属性名不符合驼峰命名法规范等问题。
那么现在我们可以使用resultMap来定义映射规则来处理这个问题,当然它还可以进行级联更新和定制类型转化器等功能。定义映射规则就是SQL到Java Bean的映射关系定义。
下面演示一下resultMap自定义结果的功能:
- 创建一个POJO类Employee
注意:类中lastName属性对应的数据库表tbl_employee字段last_namepublic class Employee { private Integer id; private String lastName; private String gender; private String email; public Employee() { super(); } public Employee(Integer id, String lastName, String gender, String email) { super(); this.id = id; this.lastName = lastName; this.gender = gender; this.email = email; } //getter,setter,toString.... }
- 在映射接口EmployeePlus中定义如下方法
public Employee getEmpById(Integer id);
- 在映射器文件EmployeePlus.xml文件定义如下SQL配置
- 测试代码
@Test public void test05() throws IOException { //1.根据MyBatis的配置文件,即mybatis-config.xml创建SqlSessionFactory SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); //2.获取session实例,能直接执行*已经映射的SQL语句* SqlSession session = sqlSessionFactory.openSession(false);//设置默认不提交 try { //3.获取接口的实现类对象 EmployeeMapperPlus mapper = session.getMapper(EmployeeMapperPlus.class); //4.查询 Employee emp = mapper.getEmpById(1); System.out.println(emp); //5.手动提交 session.commit(); } finally { //4.关闭会话session session.close(); } }
- 运行结果
在进行了自定义映射配置之后,即使Java Bean的属性名与数据库表的字段不一样,也可以形成映射关系。
五、关联查询
需求:在查询出员工对象的同时,查询出该员工所在的部门。
级联属性封装结果集
- 创建两个Java Bean,分别为员工类Employee和部门类Department
public class Employee { private Integer id; private String lastName; private String gender; private String email; private Department dept; //引用部门对象 public Employee() { super(); } public Employee(Integer id, String lastName, String gender, String email) { super(); this.id = id; this.lastName = lastName; this.gender = gender; this.email = email; } //getter,setter,toString }
public class Department { private Integer id; //部门编号 private String departmentName; //部门名称 public Department() { super(); } public Department(Integer id, String departmentName) { super(); this.id = id; this.departmentName = departmentName; } //getter,setter,toString.... }
- 创建对应的数据库表tbl_employee和tbl_dept
CREATE TABLE tbl_employee ( id int(11) NOT NULL AUTO_INCREMENT, last_name varchar(255) DEFAULT NULL, gender char(1) DEFAULT NULL, email varchar(255) DEFAULT NULL, d_id int(11) DEFAULT NULL, PRIMARY KEY (id), KEY FK_tbl_employee (d_id), CONSTRAINT FK_tbl_employee FOREIGN KEY (d_id) REFERENCES tbl_dept (id) )
CREATE TABLE tbl_dept( id INT(11) PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(255) )
- 映射接口EmployeeMapperPlus中定义如下方法
public Employee getEmpAndDept(Integer id);
- 映射器文件EmployeeMapperPlus.xml进行如下配置
<!-- 联合查询,**级联属性封装结果集** --> <resultMap type="com.atguigu.mybatis.bean.Employee" id="MyDifEmp"> <id column="id" property="id"/> <result column="last_name" property="lastName"/> <result column="gender" property="gender"/> <result column="email" property="email"/> <!-- 下面的是关于部门表的内容,可以使用Employee的属性dept来引用 --> <result column="did" property="dept.id"/> <result column="dept_name" property="dept.departmentName"/> </resultMap> <!-- 查询两个表的所有字段的值 resultMap的值即为上方resultMap元素的id--> <select id="getEmpAndDept" resultMap="MyDifEmp"> SELECT e.id id, e.last_name last_name,e.gender gender, e.email email, e.d_id d_id, d.id did, d.dept_name dept_name FROM tbl_employee e, tbl_dept d WHERE e.d_id=d.id AND e.id=#{id} </select>
- 运行测试
@Test public void test05() throws IOException { //1.根据MyBatis的配置文件,即mybatis-config.xml创建SqlSessionFactory SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); //2.获取session实例,能直接执行*已经映射的SQL语句* SqlSession session = sqlSessionFactory.openSession(false);//设置默认不提交 try { //3.获取接口的实现类对象 EmployeeMapperPlus mapper = session.getMapper(EmployeeMapperPlus.class); //4.查询 Employee emp = mapper.getEmpAndDept(1); System.out.println(emp);//连同部门信息也会打印出来 //5.手动提交 session.commit(); } finally { //4.关闭会话session session.close(); } }
刚才对映射器文件EmployeeMapperPlus.xml进行配置时使用的是级联属性封装结果集 的方式,下面我们也可以使用association的方式来定义关联的单个对象的封装规则。
association元素
association嵌套结果集
在原有的基础上,对映射器文件EmployeeMapperPlus.xml的resultMap元素进行如下更改配置:
association分步查询
我们还可以使用association来进行分步查询。
具体思路:
- 先按照员工的id去查询员工的信息
- 根据查询员工信息中的d_id值去部门表查出部门信息
- 将查询到的部门信息设置到员工信息中
具体步骤:
- 为了根据员工的id去查询员工的信息,我们需要先在映射接口EmployeeMapperPlus中定义如下方法:
public Employee getEmpByIdStep(Integer id);
- 然后需要在映射器文件EmployeeMapperPlus.xml文件进行如下配置
<!-- 查出员工的信息 --> <select id="getEmpByIdStep" resultMap="MyEmpByStep"> select * from tbl_employee where id=#{id} </select>
- 因为需要根据员工信息中的d_id值去部门表查出部门信息 ,所以现在需要在映射接口DepartmentMapper中定义如下方法:
public Department getDeptById(Integer id);
- 同样地,在映射器文件中进行如下配置:
<mapper namespace="com.atguigu.mybatis.mapper.DepartmentMapper"> <select id="getDeptById" resultType="com.atguigu.mybatis.bean.Department"> select id,dept_name departmentName from tbl_dept where id=#{id} </select> </mapper>
- 对映射器文件EmployeeMapperPlus.xml做如下的resultMap元素配置:
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmpByStep"> <!-- 员工基本属性信息映射配置 --> <id column="id" property="id"/> <result column="last_name" property="lastName"/> <result column="email" property="email"/> <result column="gender" property="gender"/> <!-- association定义关联对象的封装规则 property的内容的Employee关联的对象属性名dept select表明当前属性调用select指定的方法查出的结果,内容的namespace+方法id column指定将哪一列的值传给这个方法,这里要根据d_id去查询部门信息,所以为d_id 流程:使用select指定的方法并通过传入column指定的这列参数的值去查出对象, 并封装给property指定的属性,即dept --> <association property="dept" select="com.atguigu.mybatis.mapper.DepartmentMapper.getDeptById" column="d_id"> </association> </resultMap>
- 运行测试
延迟加载问题
从以上的输出结果可以知道,系统会进行通过员工id查询员工信息,并且通过员工的id去查询对应的部门号。但是如果我们在未使用到部门的信息时,也就是我们只输出员工本身的一些与部门无关的信息,系统依旧会进行部门信息的查询。这样会浪费系统资源,我们可以通过设置延迟加载来处理这个问题。
在全局配置文件进行如下配置:
<!-- 开启延迟加载和关闭属性按需加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
collection元素
需求:根据id查询部门的时候,同时查询出该部门的所有员工。
collection嵌套结果集
- 更改JavaBean类Department,增加字段以及对应的getter和setter方法。具体如下所示:
- 在映射接口DepartmentMapper中添加如下方法
public Department getDeptAndEmpsByDeptId(Integer id);
- 在映射器文件DepartmentMapper.xml进行如下配置
<!-- type的内容即为返回值类型,即查询到的部门对象 --> <resultMap type="com.atguigu.mybatis.bean.Department" id="MyDept"> <id column="did" property="id"/> <result column="dept_name" property="departmentName"/> <!-- collection定义关联集合类型的属性的封装规则 ofType指定集合里面的元素类型 --> <collection property="emps" ofType="com.atguigu.mybatis.bean.Employee"> <!-- 定义这个集合中元素的封装规则,这里的是员工 --> <id column="eid" property="id"/> <result column="last_name" property="lastName"/> <result column="email" property="email"/> <result column="gender" property="gender"/> </collection> </resultMap> <select id="getDeptAndEmpsByDeptId" resultMap="MyDept"> select d.id did,d.dept_name,e.id eid,e.last_name,e.email,e.gender from tbl_dept d LEFT JOIN tbl_employee e ON d.id=e.d_id WHERE d.id=#{id} </select>
- 测试代码
collection分步查询
具体思路:
- 先按照部门的id去查询部门的信息
- 根据查询到的部门信息中的id值去查询员工表的员工信息
- 将查询到的员工信息设置到部门信息的属性集合中
具体步骤:
- 在映射接口文件DepartmentMapper中定义如下方法,即根据部门id查询部门信息:
public Department getDeptByIdStep(Integer id);
- 在映射器文件DepartmentMapper.xml进行如下配置:
<select id="getDeptByIdStep" resultMap="MyDeptStep"> select id,dept_name departmentName from tbl_dept where id=#{id} </select>
- 在映射接口EmployeeMapperPlus中定义如下方法,即根据部门id查询所有员工:
public List<Employee> getEmpsByDeptId(Integer deptId);
- 在映射器EmployeeMapperPlus.xml中配置如下信息
<!--注意:如果Employee中的字段与数据库表的字段名不一样,需要给字段命和POJO中对应属性一样的名称, 比如下面的last_name字段必须需命名为lastName,否则无法完成一一映射。--> <select id="getEmpsByDeptId" resultType="com.atguigu.mybatis.bean.Employee"> select id,last_name lastName,gender,email from tbl_employee where d_id=#{deptId} </select>
- 在映射器文件DepartmentMapper.xml中配置resultMapper信息,完成关联对象的封装规则:
补充:我们上面的例子只在column属性中传递了一个部门的id值。如下图示:
我们在分步查询时也可以进行多列传值,MyBatis会将多列的值封装为map进行传递。
格式为column="{key1=column1,key2=column2}"
比如可以写成如下形式:
其中的键deptId对应映射器EmployeeMapperPlus.xml中的SQL的传入的id名称。即
六、sql 元素
sql元素的作用在于可以定义一条SQL的一部分,方便后面的SQL引用它。假设映射器文件中有如下配置信息:
我们可以使用sql将其中的id,last_name lastName,gender,email抽取出来。如下所示:
更改原有的配置信息,引用sql标签的配置。
sql元素还支持变量的传递,映射器的配置信息如下:
在include元素中定义了一个命名为e的变量,其值是SQL中标tbl_employee的别名emp,然后sql元素就能够使用这个变量名了。这个用法常用于多表查询并需要给不同表起别名的情况。