mapengpeng1999@163.com MyBatis的SQL映射文件

SQL映射文件

增删改查

接口中增删改方法不要用void,建议用int long boolean等类型。
只有查询才有查询返回结果集类型resultType,如resultType=“int”
resultType=“com.wanbangee.entities.BookCar”

在Dao接口中,定义增删改查方法。
public interface EmpDao {
public Emp selectEmpByEmpId(Integer empId);//根据empId查询雇员信息,只有一笔数据。
public List<Emp> selectEmpByEmpNameLike(String empName);//根据empName模糊查询,有多笔数据。
public void insertEmp(Emp emp);
public void updateEmp(Emp emp);
public void deleteEmp(Integer empId);
}
编写sql映射文件,编写sql
<?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="com.wanbangee.dao.EmpDao">
<!-- public Emp selectEmpByEmpId(Integer empId);-->
<select id="selectEmpByEmpId" resultType="com.wanbangee.entities.Emp" >
	select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp where emp_id = #{empId}
</select>
	<!-- select 标签: 表示的是一个查询的sql
		- id 表示是Dao接口中某一个方法的名称
		- resultType  表示这个条sql应该返回的值的类型,使用别名或者全类名,建议使用全类名
		- sql中的 #{}表示接受传递参数,参数名可随便写但最好有语义
		emp_name like #{abc}也行,但最好有语义emp_name like #{empName}
	 -->
<!-- public List<Emp> selectEmpByEmpNameLike(String empName); -->
<!-- 注意点:如果返回的是一个对象的集合,那么sql映射文件中的resultType属性应该是集合中存储对象的类型,而不是集合的类型 -->
<select id="selectEmpByEmpNameLike" resultType="com.wanbangee.entities.Emp">
select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp where emp_name like #{abc}
</select>
	
<!-- 新增,public void insertEmp(Emp emp); -->
如果传入的参数为实体类的对象,那么在sql中,必须使用#{实体类JavaBean风格的属性名} 进行参数绑定
<insert id="insertEmp">
		insert into emp(emp_name,emp_mail,emp_gender,dept_id) 
		values(#{empName},#{empMail},#{empGender},#{deptId})
</insert>
	
<!-- 修改,public void updateEmp(Emp emp); -->
	<update id="updateEmp">
		update emp set emp_name = #{empName},emp_mail = #{empMail},
		emp_gender = #{empGender},dept_id = #{deptId} where emp_id = #{empId}
	</update>
	
<!-- 删除,public void deleteEmp(Integer empId); -->
	<delete id="deleteEmp">
		delete from emp where emp_id = #{empId}
	</delete>
</mapper>
对于增删改来说,我们完成了,但是我们如何知道增删改是否成功了呢?
实际上MyBatis已经为我们封装好了Integer,int,Long,long,Boolean,boolean 类型参数,如果我们接口中的增删改方法需要获取这些的参数,
我们只需要修改增删改的方法,将返回值设置为对应类型的返回值即可,而且sql映射文件不必做任何修改。Integer,int,Long,long返回值类型返回的是影响数据的笔数,
Boolean,boolean表示影响数据笔数为0的情况下返回false,否则返回true。
对于增删改来说,MyBatis帮我们自动封装结果集
	 *  int : 表示增删改影响的数据笔数
	 *  long: 表示增删改影响的数据笔数
	 *  boolean : 表示增删改影响的数据笔数如果为0 则返回false,否则返回true。
(增删改操作都要事务管理(提交或回滚事务)sqlSession.commit();)
public int insertEmp(Emp emp);
public long updateEmp(Emp emp);
public boolean deleteEmp(Integer empId);
增删改结果集MyBatis自动封装了。
查询多笔数据返回集合,resultType类型是集合中保存对象的类型,而不是集合的类型。
增删改操作可以不用resultType接受返回值类型,要是用的话就用别名,如resultType="int"
JUnit Test Case测试类:(增删改操作都要事务管理(提交或回滚事务)sqlSession.commit();)

public class SQLMapperTest {
	SqlSession sqlSession = null;
	
	//所有测试方法执行之前都会执行
	@Before
	public void setUp() throws Exception {
		// 获取全局配置文件文件的字节输入流
		InputStream input = Resources.getResourceAsStream("mybatis.xml");
		// 创建SqlSessionFactory对象
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(input);
		// 获取SQLSession对象
		sqlSession = sqlSessionFactory.openSession();
	}

	//所有测试方法执行之后都会执行
	@After
	public void tearDown() throws Exception {
		sqlSession.close();
	}

	@Test
	public void testInsert() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		//准备数据
		Emp emp = new Emp(); //调无参构造器,再手动设置属性值进去
		emp.setDeptId(1);
		emp.setEmpGender(1);
		emp.setEmpMail("jjm_ff@163.com");
		emp.setEmpName("姜建民");
		
		int count = empDao.insertEmp(emp); //执行新增操作,返回影响的数据笔数
		
		System.out.println(count + "----------------------------");
		System.out.println(emp);
		this.sqlSession.commit();//提交事务
	}
	
	@Test
	public void testUpdate() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		
		//准备数据
		Emp emp = new Emp(); //调无参构造器,再手动设置属性值进去
		emp.setEmpId(16);
		emp.setDeptId(1);
		emp.setEmpGender(1);
		emp.setEmpMail("544130858@QQ.com");
		emp.setEmpName("JianmingJiang");
		
		long count = empDao.updateEmp(emp);//执行修改操作,返回影响的数据笔数
		System.out.println(count + "----------------------------");
		System.out.println(emp);
		this.sqlSession.commit();//提交事务
	}
	
	@Test
	public void testDelete() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		
		boolean flag = empDao.deleteEmp(16);//执行删除操作,返回是否影响数据情况,true删除成功,false删除失败。
		
		System.out.println(flag + "----------------------------");
		
		this.sqlSession.commit();//提交事务
	}
	
	@Test
	public void testSelectEmpByEmpNameLike() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		String empName =  "%jm%";
		List<Emp> emps = empDao.selectEmpByEmpNameLike(empName);
		System.out.println(emps);
	}
	
	@Test
	public void testSelectEmpByEmpId() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		Emp emp = empDao.selectEmpByEmpId(2);
		System.out.println(emp);
	}
}
模糊查询like语句该怎么写?
第1种:在Java代码中添加sql通配符。
String empName =  "%jm%";
List<Emp> emps = empDao.selectEmpByEmpNameLike(empName);

<select id="selectEmpByEmpNameLike" resultType="com.wanbangee.entities.Emp">
select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp where emp_name like #{empName}
</select>

第2种:在sql语句中拼接通配符,会引起sql注入。
String empName =  "jm";
List<Emp> emps = empDao.selectEmpByEmpNameLike(empName);

<select id="selectEmpByEmpNameLike" resultType="com.wanbangee.entities.Emp">
select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp where emp_name like "%"#{empName}"%"
</select>

MYSQL获取自增主键的值

MySQL是支持主键自增的,在开发中,有这样的需求,传递一个没有主键值的实体类对象进行新增,新增完成之后,
需要这个实体类将新的主键封装上。如下所示:
Emp -- > insert(新增)

Java程序中对象

新增之前
emp_id emp_name emp_mail emp_gender dept_id
		张三	  zs@163.com   	1		1
新增之后
emp_id emp_name emp_mail emp_gender dept_id
  10	  张三	 zs@163.com	   1	 1
实际上MyBatis也提供了相应的操作。在SQL映射文件中加配置
<!-- public void insertEmp(Emp emp);
	如果传入的参数为实体类的对象,那么在sql中,必须使用#{实体类JavaBean风格的属性名} 进行参数绑定
	获取自增主键的值:
		useGeneratedKeys : 表示是否通过自增主键获取主键值
				- true 是
				- false 否                             												
		keyColumn : 数据表中的主键列名,keyColumn="emp_id" 【可以省略,在联合主键的情况下不能省】
		//联合主键,指表的主键列有多个
		keyProperty : 对应主键列的实体类的属性名,keyProperty="empId"
-->
	 <insert id = "insertEmp" keyColumn="emp_id" keyProperty="empId" useGeneratedKeys="true">
	 	insert into emp(emp_name,emp_mail,emp_gender,dept_id) 
	 	values(#{empName},#{empMail},#{empGender},#{deptId})
	 </insert>
	 
	 
	@Test
	public void testInsert() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		//准备数据
		Emp emp = new Emp();
		emp.setDeptId(1);
		emp.setEmpGender(1);
		emp.setEmpMail("jjm_ff@163.com");
		emp.setEmpName("姜建民");
		
		int count = empDao.insertEmp(emp); //执行新增操作
		
		System.out.println(count + "----------------------------");
		System.out.println(emp);
		this.sqlSession.commit();//提交事务
	}

Oracle中使用序列来完成自增主键值的获取

Oracle数据库中,并没有主键自增这个说法,而是通过序列完成。

create table emp_721(
       emp_id number(11) primary key,
       emp_name varchar2(20) not null,
       emp_mail varchar2(30) not null,
       emp_gender number(1) not null,
       dept_id number(10) not null
);

insert into emp_721 (emp_id,emp_name,emp_mail,emp_gender,dept_id)
values(1,'张大炮','zdp@qq.com',1,1);

select * from emp_721;

-- 以下sql报错,那么如何实现主键自增呢
insert into emp_721 (emp_name,emp_mail,emp_gender,dept_id)
values('张益达','zdp@qq.com',1,1);

-- oracle数据库中要实现主键自增,必须使用序列
create sequence seq_2020721;

-- sequence查询
select seq_2020721.nextval from dual; -- 查询下一个值

select seq_2020721.currval from dual; -- 查询序列当前的使用的值

-- 所以开发中,Oracle数据库表数据新增,对Number的主键来说,都使用序列进行自增
insert into emp_721 (emp_id,emp_name,emp_mail,emp_gender,dept_id) 
values(seq_2020721.nextval,'张大炮','zdp@qq.com',1,1);

获取(Oracle)非自增主键的值

BEFORE版:先查询后新增数据
<!--
	Oracle环境下获取非自增主键的值 
	public void insertEmp(Emp emp);
-->
<insert id="insertEmp" databaseId="oracle">
	 	<!-- 
	 		selectKey : 表示查询一个值,作为新增数据的主键
	 			keyColumn : 数据表中的主键列名
				keyProperty : 对应主键实体类的属性名
				order:表示是在新增之前执行查询还是新增之后执行查询
					BEFORE : 表示新增之前
					AFTER : 表示新增之后
	 	 -->
	 <selectKey keyColumn="emp_id" keyProperty="empId" resultType="int" order="BEFORE">
	 	select seq_2020721.nextval from dual
	 </selectKey>
	 	insert into emp_721(emp_id,emp_name,emp_mail,emp_gender,dept_id) 
	 	values(#{empId},#{empName},#{empMail},#{empGender},#{deptId})
</insert>
AFTER版:先新增数据后查询
<!--
		Oracle环境下获取非自增主键的值 
		public void insertEmp(Emp emp);
-->
	 <insert id="insertEmp" databaseId="oracle">
	 	<!-- 
	 		selectKey : 表示查询一个值,作为新增数据的主键
	 			keyColumn : 数据表中的主键列名
				keyProperty : 对应主键实体类的属性名
				order:表示是在新增之前执行查询还是新增之后执行查询
					BEFORE : 表示新增之前
					AFTER : 表示新增之后
	 	 -->
	 	<selectKey keyColumn="emp_id" keyProperty="empId" resultType="int" order="AFTER">
	 		select seq_2020721.currval from dual
	 	</selectKey>
	 	     insert into emp_721(emp_id,emp_name,emp_mail,emp_gender,dept_id) 	                      values(seq_2020721.nextval,#{empName},#{empMail},#{empGender},#{deptId})
	 </insert>

参数的处理

接口方法入参中的普通参数:
在正常的开发中,参数可能是单个参数,也可能是多个参数,也可能是实体类的对象,这些是较常见的,还有一些特殊的,比如参数是map键值对,list集合。

建议:接口方法入参中所有的普通参数,尽量都写上@Param注解,尤其是多个参数时,必须写上。
1.接口方法入参中所有的普通参数:
1.1.接口参数为1个的情况:
单个参数MyBatis不会做任何特殊处理,在SQL映射文件中使用任意的参数名称都可以取得该参数。
public interface EmpDao {
	public Emp selectEmpByEmpId(Integer empId);
}
<select id="selectEmpByEmpId" resultType="com.wanbangee.entities.Emp" >
		select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp where emp_id = #{id}
</select>
			<!-- where emp_id = #{empId},接口参数为1个的情况时,
			这里#{}使用任意的参数名称都可以取得该参数,如where emp_id = #{id}-->
@Test
public void testselectEmpByGenderAndDeptID() throws IOException {
	//获得接口的动态代理实现类
	EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
	Emp emp = empDao.selectEmpByEmpId(1);
	System.out.println(emp);
}

1.2.接口参数为2个或者多个的情况:
	public interface EmpDao {
		//根据性别及部门Id查询
		public List<Emp> selectEmpByEmpGenderAndDeptId(Integer empGender,Integer deptId);
	}
	<select id="selectEmpByEmpGenderAndDeptId" resultType="com.wanbangee.entities.Emp">
		select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp 
		where emp_gender = #{empGender} and dept_id = #{deptId}
	</select>
	@Test
	public void testselectEmpByGenderAndDeptID() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		List<Emp> emps = empDao.selectEmpByEmpGenderAndDeptId(0, 1);
		System.out.println(emps);
	}
运行程序会报错,Error querying database.  Cause: org.apache.ibatis.binding.BindingException: Parameter 'empGender' not found. Available parameters are [0, 1, param1, param2]	
说参数只有0,1,param1,param2可用,这是因为在传递多个参数的情况下,
MyBatis会将多个参数自动的封装成Map键值对,每个参数都会封装两个 key-value,这两个键不同,但是值相同。
	在我们的SQl映射文件中,#{键},在执行的时候就会传入相应的键对应的值
	<select id="selectEmpByEmpGenderAndDeptId" resultType="com.wanbangee.entities.Emp">
		select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp 
		where emp_gender = #{0} and dept_id = #{param2}
	</select>
也可以写下面这样的 where emp_gender = #{0} and dept_id = #{1}
或者这样的 where emp_gender = #{param1} and dept_id = #{param2}
- 比如有两个参数,值为  abc  和  2
		key			value
		0			abc
		1			2
		param1		abc
		param2		2
- 然后在SQL映射文件中,就可以使用#{key}获得参数的值
- 如果现在想要给参数指定一个key,可以在接口入参中使用@Param注解指定,
		如(@Param("gender")Integer empGender,@Param("id")Integer deptId),封装结果为:
		key			value
		gender			
		id
		param1		
		param2
		发现默认封装的param1,param2有效,但是0 和  1 无效了
		
	@Test
	public void testselectEmpByGenderAndDeptId() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		List<Emp> emps = empDao.selectEmpByEmpGenderAndDeptId(0, 1);
		System.out.println(emps);
	}	
	public interface EmpDao {
		//根据性别及部门Id查询
		public List<Emp> selectEmpByEmpGenderAndDeptId
		(@Param("gender")Integer empGender,@Param("id")Integer deptId);
	}
<select id="selectEmpByEmpGenderAndDeptId" resultType="com.wanbangee.entities.Emp">
		select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp 
	    where emp_gender = #{gender} and dept_id = #{id}
</select> 
 where emp_gender = #{param1} and dept_id = #{param2}   //param1,param2有效
 where emp_gender = #{0} and dept_id = #{1}   //0和1无效
 where emp_gender = #{gender} and dept_id = #{param2}
 和where emp_gender = #{param1} and dept_id = #{id}交替都有效
 在接口方法入参中使用@Param注解给参数指定一个key,那么默认的param1,param2...或者0,1...还能用吗?
 param1,param2...可以正常使用,0,1,...不能使用了。

总结

不要使用0,1。要用的话就用param1,param2。因为最新的MyBatis版本不支持0,1,而是arg0,arg1
建议:接口方法入参中所有的普通参数,尽量都写上@Param注解,尤其是多个参数时,必须写上。

接口参数为2个或者多个的情况,再来个案例演练,模糊查询并将结果进行分页。
public List<Emp> selectEmpByEmpNameLike
(String empName,Integer pageCurrent,Integer pageSize);
<select id="selectEmpByEmpNameLike" resultType="com.wanbangee.entities.Emp">
	select * from emp where emp_name like #{param1} limit #{1},#{param3}
</select>
	@Test
	public void testselectEmpByEmpNameLike() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		List<Emp> emps = empDao.selectEmpByEmpNameLike("%jm%", 1, 3);
		System.out.println(emps);
	}
接口方法入参中为一个实体类对象:
2.接口的参数为一个实体类对象:(像之前的新增,修改)
MyBatis会将参数封装成一个Map键值对,键对应的是实体类对象的属性名,值对应的是该实体类对象的属性值。
- 比如 Emp emp = new Emp(); 
emp.empName = "abc" empId = 2 empGender = 1 empMail="jjm@163.com" deptId=1
		key					value
		empName				abc
		empId				2
		empGender			1
		empMail				jjm@163.com
		deptId              1
- 而后在SQL映射文件中使用#{key} 就可以获得参数了
比如,传递对象为Emp emp,则封装的Map键值对为:
Keyvalue
empNameempName属性值
empMailempMail属性值
empIdempId属性值
empGenderempGender属性值
deptIddeptId属性值
接口方法入参中为一个集合:
3.接口的参数为一个集合  如 public List<Emp> selectEmpByEmpNames(List<String> empNames);
- MyBatis会将集合封装成一个Map,默认情况如下
		key							value
		collection					传入的集合
		list						传入的集合
		那么在SQL映射文件中使用#{collection[索引]}或者{list[索引]} 获得传入的集合的值
public interface EmpDao {
	public List<Emp> selectEmpByEmpNames(List<String> empNames);
}
<select id="selectEmpByEmpNames" resultType="com.wanbangee.entities.Emp">
		select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp 
		where  emp_name in(#{collection[0]},#{list[1]},#{list[2]})
</select>
	@Test
	public void testselectEmpByEmpNames() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		List<String> empNames = new ArrayList<>();
		empNames.add("aa");
		empNames.add("bb");
		empNames.add("cc");
		List<Emp> emps = empDao.selectEmpByEmpNames(empNames);
		System.out.println(emps);
	}
接口方法入参参数有多个,其中存在集合:
4. 接口的参数有多个,其中存在集合如  
public List<Emp> selectEmpByEmpNamesAndEmpGender(List<String> empNames,Integer gender);
	- 参照1中多个参数进行处理,会将多个参数封装成Map
		key					value
		param1				集合
		param2				gender的值
		0					集合
		1					gender的值
		那么在SQL映射文件中使用#{param1[索引]}或者{0[索引]} 获得传入的集合的值
public interface EmpDao {
	public List<Emp> selectEmpByEmpNamesAndEmpGender
	(List<String> empNames,Integer gender);
}
<select id="selectEmpByEmpNamesAndEmpGender" resultType="com.wanbangee.entities.Emp">
		select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp 
		where  emp_name in(#{param1[0]},#{0[1]},#{param1[2]}) and emp_gender = #{param2}
</select>
	@Test
	public void testselectEmpByEmpNamesAndGender() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		List<String> empNames = new ArrayList<>();
		empNames.add("aa");
		empNames.add("bb");
		empNames.add("cc");
		List<Emp> emps = empDao.selectEmpByEmpNamesAndEmpGender(empNames,1);
		System.out.println(emps);
	}
接口方法入参参数有多个,其中存在实体类对象:
5. 接口的参数有多个,其中存在实体类对象如 
public List<Emp> selectEmpByEmpAndEmpGender(Emp emp,Integer gender);
	- 参照1中多个参数进行处理,会将多个参数封装成Map
		key					value
		param1				Emp对象
		param2				gender的值
		0					Emp对象
		1					gender的值
		那么在SQL映射文件中使用#{param1.属性名}或者{0.属性名} 获得传入的实体类的属性值
public interface EmpDao {
	public List<Emp> selectEmpByEmpAndEmpGender(Emp emp,Integer gender);
}
<select id="selectEmpByEmpAndEmpGender" resultType="com.wanbangee.entities.Emp">
		select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp 
		where  emp_name =#{0.empName} and emp_gender = ${param2}
</select>
	@Test
	public void testselectEmpByEmpAndGender() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		Emp emp = new Emp();
		emp.setEmpName("aa");
		List<Emp> emps = empDao.selectEmpByEmpAndEmpGender(emp, 1);
		System.out.println(emps);
	}
接口方法入参为一个Map键值对(Map<String,Object>):
6. 接口参数为一个Map键值对(Map<String,Object> map),
传入参数为Map的情况下,直接在sql映射文件中使用#{键} 就可以传递相应的值

1、在接口方法中,参数直接传递Map;
public selectEmpByAccNameAndAccPass(Map<String,Object> map);
2、编写sql语句的时候,需要传递参数类型,参数类型为map
<select id="selectEmpByAccNameAndAccPass" resultType="com.wanbangee.entities.Emp">
select * from emp where acc_name = #{accName} and acc_pass = #{accPass}
</select>
3、在使用方法的时候,Map的 key 为 sql中取的值即可,没有顺序要求!
Map<String, Object> map = new HashMap<String, Object>();
map.put("accName","小明");
map.put("accPass","123456");
Emp emp = empDao.selectEmpByAccNameAndAccPass(map);

总结:如果参数过多,我们可以考虑直接使用Map实现,如果参数比较少,直接传递参数即可。
接口所有的普通参数,尽量都写上@Param注解,尤其是多个参数时,必须写上。

7. 接口参数为一个数组,那么SQL映射文件中使用 #{array[索引]}就可以获得数组的值
8. 接口参数为一个Set集合,那么在SQL映射文件中使用#{collection[索引]}或者{set[索引]} 获得传入的集合的值

关于#和$的区别

- # 使用?占位符的形式传递参数,使用预处理【预编译】的处理完成sql的执行,类似于PreparedStatement
		- 安全的,不存在sql注入的问题
- $ 直接通过字符串拼接的形式传递参数,使用的是sql拼接的形式将参数直接拼接在sql中,类似于Statement的形式
		- 不安全的,存在sql注入的问题 (where emp_name = abc or 1=1)
运行程序发现,#预处理的形式会以?形式,而$是sql拼接的形式将参数直接拼接在sql中
select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp 
		where emp_gender = #{0} and dept_id = ${1}
select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp 
		where emp_gender = ? and dept_id = 1 

那么什么时候使用#,什么时候使用$呢?参数为查询条件的时候使用#,而分库分表的时候使用$,什么是分库分表呢?
	$ 在分库分表的情况下还是经常使用的,比如一个大型的企业,每个月的工资数据都存在的不同的工资表中
		2020年11月的工资   salary_202011 表
		2020年12月的工资  salary_202012表
		现在需求 : 按照传入的月份查询工资
		select * from salary_#{yearMonth} --> select * from salary_?
		select * from salary_${yearMonth} --> select * from salary_202012
	再比如:在查询结果排序的时候,可能会根据可以点击的某个数据列进行排序,传入的参数就是数据列名
select * from 表名 order by #{cloummName} --> select * from 表名 order by ?
select * from 表名 order by ${cloummName} --> select * from 表名 order by 具体传入的数据列名

案例:
public interface EmpDao {
	public List<Emp> selectEmp(String tableName,String orderCloumn);//表名,排序列名
}
	@Test
	public void testselectEmp() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		Emp emp = new Emp();
		emp.setEmpName("aa");
		List<Emp> emps = empDao.selectEmp("emp", "emp_name");
		System.out.println(emps);
	}
<select id="selectEmp" resultType="com.wanbangee.entities.Emp">
	select emp_id,emp_name,emp_mail,emp_gender,dept_id from #{param1} order by #{param2}
</select>  

select emp_id,emp_name,emp_mail,emp_gender,dept_id from ? order by ?
用from #{param1} order by #{param2}会报错

<select id="selectEmp" resultType="com.wanbangee.entities.Emp">
	select emp_id,emp_name,emp_mail,emp_gender,dept_id from ${param1} order by ${param2}
</select>  

select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp order by emp_name
得用from ${param1} order by ${param2}

接口方法中查询结果返回类型为List(查询结果集为多行多列,多笔数据)

如果查询的结果集为多笔数据【每一笔数据都对应实体类的一个对象】,可以使用List接收,
那么每一条数据都会被封装为实体类的一个对象,而List中就是存放了查询结果集的多个对象。
public List<Emp> selectEmp();
<select id="selectEmp" resultType="com.wanbangee.entities.Emp" databaseId="mysql">
		select * from emp
</select>
查询多笔数据返回集合,resultType类型是集合中保存对象的类型,而不是集合的类型。

接口方法中查询结果返回类型为Map(查询结果集为单行,一笔数据)

在查询结果集映射为多个实体类对象情况下,不能使用Map接收,只能使用List接受,
只有查询结果集为单行的情况下,单行单列、单行多列都行,才能使用Map接收。
单笔数据的情况下,使用Map接收,Map的键为查询的数据列名,值为对应列名的查询结果。
public interface EmpDao {
	//根据雇员名称查询雇员信息
	public Map<String,Object> selectEmpByEmpName(String empName);//假定没有重复的名字,一个名字对应一笔数据信息
}
<select id="selectEmpByEmpName" resultType="map">
select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp where emp_name = #{empName}
</select>
	@Test
	public void testSelectEmpByEmpName() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		Map<String,Object> map = empDao.selectEmpByEmpName("aa");
		System.out.println(map);
	}
这里resultType="map"查询结果集返回的是map键值对,不再是Emp的对象
如果数据库有多个aa会报错,只有查询结果集为单行的情况下,才能使用Map接收。

查询结果集是单行单列

比如我们查询数据笔数,那么就是单行单列的值。这种情况下,可以直接将结果集定义为查询结果集的类型。
<!-- public int selectCount();-->
	 <select id="selectCount" resultType="int">
	 	select count(*) from emp
	 </select>

单行单列也可以使用Map接收:
<!-- public Map<String,Object> selectCount();-->
	 <select id="selectCount" resultType="map">
	 	select count(*) from emp
	 </select>
	@Test
	public void testSelectCount() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		Integer count = empDao.selectCount();
		System.out.println(count);
	}

自定义封装结果集映射resultMap标签

public Emp selectEmpByEmpId(Integer empId);
<select id="selectEmpByEmpId" resultType="com.wanbangee.entities.Emp" >
	select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp where emp_id = #{empId}
</select>
	@Test
	public void testSelectEmpByEmpId() throws IOException {
		//获得接口的动态代理实现类
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		Emp emp = empDao.selectEmpByEmpId(2);
		System.out.println(emp);
	}
查询的列名是以下划线形式会自动驼峰成属性名,要是自动驼峰没有生效就关联不上了,解决方案:给查询的列名取个别名。
select emp_id empId,emp_name empName,emp_mail empMaill,emp_gender empGender,
dept_id deptId from emp where emp_id = #{empId}
除了自动驼峰,给查询的sql的列取个别名,还有其它方式将查询结果封装成实体类对象吗?
自定义封装结果集映射:
<!-- resultMap自定义封装结果集
		- type : 自定义映射结果集的实体类的类型
		- id : 自定义封装结果集的名字,是唯一的
-->
<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
	 	<!-- 
	 		id标签给主键列指定映射的实体类的属性
	 			column : 查询结果集的列名
	 			property : 指定映射的实体类的属性名
	 	 -->
	 	<id column="emp_id" property="empId"/>
	 	<!-- 
	 		result标签给非主键列指定映射的实体类的属性
	 			column : 查询结果集的列名
	 			property : 指定映射的实体类的属性名
	 			column是数据库表的列名 , property是对应实体类的属性名
	 	 -->
	 	<result column="emp_name" property="empName"/>
	 	<result column="emp_mail" property="empMail"/>
	 	<result column="emp_gender" property="empGender"/>
	 	<result column="dept_id" property="deptId"/>
</resultMap>

select查询语句也要改下,查询结果集为resultMap。
在select标签,如果要使用自定义封装结果集,必须使用resultMap属性声明,
而且resultMap和resultType 不能同时存在
<select id="selectEmpByEmpId" resultMap="myEmp">
	 select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp where emp_id = #{empId}
</select>

级联属性的封装resultMap,引用其它类中属性

级联属性:dept.deptId
在Emp实体类中引用Dept,在查询Emp的同时,要求将对应引用的Dept属性也能够封装上,按照传统的写法如下:
在Emp实体类引用Dept(deptId;deptName;),private Dept dept;再加上get和set方法
<!-- public Emp selectEmpById(Integer id);-->
	<select id="selectEmpById" resultType="com.wanbangee.entities.Emp" >
		select a.emp_id,a.emp_name,a.emp_mail,a.emp_gender,a.dept_id,
		b.dept_name dept.deptName
		from emp a left join dept b on a.dept_id = b.dept_id where emp_id = #{empId}
	</select>
但是程序运行结果报错,原因在于查询数据列的别名不符合要求,那么我们可以给别名使用破折号,如下:
<!-- public Emp selectEmpById(Integer id);-->
	<select id="selectEmpById" resultType="com.wanbangee.entities.Emp" >
		select a.emp_id,a.emp_name,a.emp_mail,a.emp_gender,a.dept_id,
		b.dept_name `dept.deptName` 
		from emp a left join dept b on a.dept_id = b.dept_id where emp_id = #{empId}
	</select>
resultMap自定义封装结果集映射意义不在于单独属性的封装,而是在于级联属性:dept.deptId的封装。
<select id="selectEmpById" resultMap="myEmp" >
		select a.emp_id,a.emp_name,a.emp_mail,a.emp_gender,a.dept_id,b.dept_name 
		from emp a left join dept b on a.dept_id = b.dept_id where emp_id = #{empId}
</select>
	<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
		<result column = "dept_id" property="dept.deptId"/>
		<result column = "dept_name" property="dept.deptName"/>
	</resultMap>

association关联映射,存在笛卡尔乘积现象不会用

association是MyBatis中提供的一个多对一的关联映射的一个标签,在resultMap标签里面使用,表示多对一的关系,
在多的一端引用一的一端,比如雇员(emp)和部门(dept)的关系,每个雇员都存在于一个部门。
现在查询雇员的同时要关联对应的部门,雇员对应的部门只有一个,这个时候就可以使用association进行关联映射:
<!-- resultMap自定义封装结果集映射 
		- type : 自定义映射结果集的实体类的类型
		- id : 自定义封装结果集的名字,是唯一的
-->
	<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
	<!-- 
	 		id标签给主键列指定映射的实体类的属性
	 			column : 查询结果集的列名
	 			property : 指定映射的实体类的属性名
	  -->
	    <id column="emp_id" property="empId"/>
	    <!-- 
	 		result标签给非主键列指定映射的实体类的属性
	 			column : 查询结果集的列名
	 			property : 指定映射的实体类的属性名
	 	 -->
		<result column="emp_name" property="empName"/>
		<result column="emp_mail" property="empMail"/>
		<result column="emp_gender" property="empGender"/>
		<result column="dept_id" property="deptId"/> 
		
association : 使用之后,可以将引用的属性进行封装,比如Emp中封装Dept属性
使用association后默认的自动驼峰name="mapUnderscoreToCamelCase" value="true"就失效了

Emp实体类有dept这个属性,这个属性是这个类型javaType="com.wanbangee.entities.Dept
		使用association 完成级联属性的结果集映射
	 			property : 级联属性名称 
	 			javaType : 级联属性类型
	 			
		<association property="dept" javaType="com.wanbangee.entities.Dept">
			<id column="dept_id" property="deptId"/>
			<result column="dept_name" property="deptName"/>
		</association>
	</resultMap>
	
<!-- public Emp selectEmpByEmpId(Integer empId);-->
	<select id="selectEmpByEmpId" resultMap="myEmp" >
		select a.emp_id,a.emp_name,a.emp_mail,a.emp_gender,a.dept_id,b.dept_name  
		from emp a left join dept b on a.dept_id = b.dept_id where emp_id = #{empId}
	</select>
以上的查询虽然结果正确,但是在数据量特别大的时候,效率很低,因为关联查询一定存在笛卡尔乘积现象。
所以后期开发中,几乎不会使用这种封装形式。而是使用分步查询,没有笛卡尔乘积现象。

association分步查询,重点

	<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
		<id column="emp_id" property="empId"/>
		<result column="emp_name" property="empName"/>
		<result column="emp_mail" property="empMail"/>
		<result column="emp_gender" property="empGender"/>
		<result column="dept_id" property="deptId"/>
association是支持进行分步查询的,第一步先查询雇员信息,第二步查询对应部门信息。
		association : 使用之后,可以将引用属性进行封装,比如Emp中封装Dept属性。
		association : 分步查询
	 			property : 级联属性名称 
	 			javaType : 级联属性类型【可省略】
	 			select : 关联其他的SQL执行,调用其他的查询方法
	 			column : 将查询的列名作为关联查询的参数
	 			
在DeptDao接口中写上这个方法,
public Dept selectDeptByDeptId(Integer deptId);
在DeptDao.xml写上,
<select id="selectDeptByDeptId" resultType="com.wanbangee.entities.Dept">
	 	select dept_id,dept_name from dept where dept_id = #{deptId}
</select>

select="com.wanbangee.dao.DeptDao.selectDeptByDeptId"返回的对象就是property="dept"
但它查询需要一个where dept_id = #{deptId},
把"selectEmpByEmpId"查询到的a.dept_id 传过去column="dept_id"
javaType="com.wanbangee.entities.Dept"可省略

		<association property="dept"  javaType="com.wanbangee.entities.Dept"
              select="com.wanbangee.dao.DeptDao.selectDeptByDeptId" column="dept_id">
		</association>
	</resultMap>
	
<!-- public Emp selectEmpByEmpId(Integer empId);-->
	<select id="selectEmpByEmpId" resultMap="myEmp" >
		select a.emp_id,a.emp_name,a.emp_mail,a.emp_gender,a.dept_id 
		from emp a where a.emp_id = #{empId}
	</select>

association分步查询,延迟加载,重点

延迟加载在开发中可以大大的提升数据库的查询性能
延迟加载策略可以大大的提升数据库的查询性能,比如我们在查询Emp对象的时候,不应该将Dept查询出来,
而是要在Emp对象需要使用Dept的时候,再进行查询部门,这种查询策略叫做延迟加载策略,又称按需加载,又称懒加载。
默认情况下,MyBatis提供的分布查询策略就是即时加载,所以我们要通过配置开启延迟加载策略。
改变MyBatis运行时的行为,表示要开启延迟加载,在MyBatis全局配置文件中修改
		<!-- 开启延迟加载策略 -->
		<setting name="lazyLoadingEnabled" value="true"/> //开启懒加载,同意
		<setting name="aggressiveLazyLoading" value="false"/> //结束懒加载,不同意
添加了上述配置之后,我们分布查询就是延迟加载策略了,
那么在开启了延迟加载策略之后,某些配置需要即时加载,有怎么配置呢?我们可以在association标签中配置:

		association : 使用之后,可以将引用属性进行封装,比如Emp中封装Dept属性。
		association : 分步查询
	 			property : 级联属性名称 
	 			javaType : 级联属性类型【可省略】
	 			select : 关联其他的SQL执行,调用其他的查询方法
	 			column : 将查询的列名作为关联查询的参数
			fetchType : 设置数据提取方式(默认不写就是延迟加载)
	 			lazy : 延迟加载,懒加载,按需加载 ,使用到了部门对象的时候再查询部门对象,没有用到则不查
	 			eager : 即时加载,不管用还是不用部门,都要去查询部门对象
	-->
	<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
		<id column="emp_id" property="empId"/>
		<result column="emp_name" property="empName"/>
		<result column="emp_mail" property="empMail"/>
		<result column="emp_gender" property="empGender"/>
		<result column="dept_id" property="deptId"/>
		<association property="dept"  select="com.wanbangee.dao.DeptDao.selectDeptByDeptId" column="dept_id" fetchType="lazy">
		</association>
	</resultMap>
没有用到部门对象则不查,只执行一条sql	
	@Test
	public void testSelectEmpByEmpId() throws IOException {
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		Emp emp = empDao.selectEmpByEmpId(1);
		System.out.println(emp.getEmpMail());
	}
用到部门对象再去查,执行2条sql	
	@Test
	public void testSelectEmpByEmpId() throws IOException {
		EmpDao empDao = this.sqlSession.getMapper(EmpDao.class);
		Emp emp = empDao.selectEmpByEmpId(1);
		//System.out.println(emp.getEmpMail());
		System.out.println(emp.getDept());
	}

collection定义关联集合的封装规则

多对一:在多的一端引用一的一端作为属性,比如在Emp中引用Dept作为属性,MyBatis中使用association
一对一:在任意的一的一端,引用另外的一的一端作为属性,MyBatis中使用association
一对多:在一的一端引用多的一端,且使用集合属性,
比如在Dept中有一个Emp的集合作为属性,MyBatis中使用collection
多对多:以任意一端持有另外一端的集合,作为属性。
比如学生和老师的关系,就是典型的多对多,MyBatis中使用collection

collection分步查询及延迟加载

在实体类Dept中,private Integer deptId; private String deptName; private List<Emp> emps;
	
<!-- 根据部门ID查询雇员信息,public List<Emp> selectEmpByDeptId(Integer deptId); -->
<select id="selectEmpByDeptId" resultType="com.wanbangee.entities.Emp">
	select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp where dept_id = #{deptId}
</select>

<!-- 根据部门ID查询部门,public Dept selectDeptByDeptId(Integer deptId);	-->
	 <select id="selectDeptByDeptId" resultType="com.wanbangee.entities.Dept">
	 	select dept_id,dept_name from dept where dept_id = #{deptId}
	 </select>
<!-- 查询所有部门,public List<Dept> selectAllDept();-->
	<select id="selectAllDept" resultMap="myDept">
		select dept_id ,dept_name from dept
	</select>
<resultMap type="com.wanbangee.entities.Dept" id="myDept">
	<id column="dept_id" property="deptId"/>
	<collection property="emps" column="dept_id" 
			select="com.wanbangee.dao.EmpDao.selectEmpByDeptId" fetchType="lazy">
	</collection>
</resultMap> 
	
	@Test
	public void testSelectAllDept() throws IOException {
		DeptDao deptDao = this.sqlSession.getMapper(DeptDao.class);
		List<Dept> depts = deptDao.selectAllDept();
		System.out.println(depts);
		}
延时加载效果
	@Test
	public void testSelectAllDept() throws IOException {
		DeptDao deptDao = this.sqlSession.getMapper(DeptDao.class);
		List<Dept> depts = deptDao.selectAllDept();
		System.out.println(depts.get(0).getDeptName());
		}
执行2条sql	
	@Test
	public void testSelectAllDept() throws IOException {
		DeptDao deptDao = this.sqlSession.getMapper(DeptDao.class);
		List<Dept> depts = deptDao.selectAllDept();
		System.out.println(depts.get(0).getEmps());
		}

分步查询传递多列值的情况,用的少

不管是association还是collection,上面的程序只传递了一列值作为分布查询的查询条件参数,
在开发中,还存在另外一种情况,分布查询值传递两个参数,也就意味着传递两列值作为分布查询的查询条件参数
 <resultMap type="com.wanbangee.entities.Dept" id="myDept">
		<id column="dept_id" property="deptId"/>
		<!-- 
			{deptId=dept_id,deptName=dept_name} : 
			表示传递多个查询列作为下一个查询的参数,参数封装后的Map为:
				key				value
				deptId			dept_id
				detpName		dept_name
		 -->
		<collection property="emps" column="{deptId=dept_id,deptName=dept_name}" select="com.wanbangee.dao.EmpDao.selectEmpByDeptId" fetchType="lazy">
		</collection>
	</resultMap>
	
	 		在传递多列值的情况下使用{abc=dept_id,bcd=dept_name},根据参数规则,
	 		多个参数在MyBatis中会封装了Map键值对,此时Map封装的效果如下:
	 		key ======================value
	 		abc					  dept_id数据列的值
	 		bcd					  dept_name数据列的值
	public class Dept {
	private Integer deptId;
	private String deptName;
	private List<Emp> emps;
	
	public class Emp {
	private Integer empId;
	private String empName;
	private String empMail;
	private Integer empGender;
	private Integer deptId;
	private Dept dept;

public interface EmpDao {
	public List<Emp> selectEmpByDeptId(Integer deptId,String deptName);
}
<select id="selectEmpByDeptId" resultType="com.wanbangee.entities.Emp">
select emp_id,emp_name,emp_mail,emp_gender,dept_id from emp where dept_id = #{deptName}
</select>
//这里where dept_id = #{deptName}查不到结果,只是看下参数deptName有没有传过来,换成deptId有查询信息


public interface DeptDao {
	public Dept selectDeptByDeptId(Integer deptId);
	public List<Dept> selectAllDept();
}
<select id="selectDeptByDeptId" resultType="com.wanbangee.entities.Dept">
	 	select dept_id,dept_name from dept where dept_id = #{deptId}
	 </select>
	 
	 <!-- 查询所有部门,public List<Dept> selectAllDept();-->
	<select id="selectAllDept" resultMap="myDept">
		select dept_id ,dept_name from dept
	</select>
	<resultMap type="com.wanbangee.entities.Dept" id="myDept">
		<id column="dept_id" property="deptId"/>
		<!-- 
			{deptId=dept_id,deptName=dept_name} : 
			表示传递多个查询列作为下一个查询的参数,参数封装后的Map为:
				key				value
				deptId			dept_id
				detpName		dept_name
		 -->
		<collection property="emps" column="{deptId=dept_id,deptName=dept_name}" select="com.wanbangee.dao.EmpDao.selectEmpByDeptId" fetchType="lazy">
		</collection>
	</resultMap>
		
	@Test
	public void testSelectAllDept() throws IOException {
		DeptDao deptDao = this.sqlSession.getMapper(DeptDao.class);
		List<Dept> depts = deptDao.selectAllDept();
		System.out.println(depts.get(0).getEmps());
	}

鉴别器,使用较少

鉴别器使用较少,所谓鉴别器表示可以根据某列值的情况,更改封装行为
比如:如果部门名称为AA,则不查询这个部门的员工,如果部门名称不为AA,则查询这个部门的所有员工,相当于if条件
<resultMap type="com.wanbangee.entities.Dept" id="myDept">
		<id column="dept_id" property="deptId"/>
		<!-- 
			{deptId=dept_id,deptName=dept_name} : 
			表示传递多个查询列作为下一个查询的参数,参数封装后的Map为:
				key				value
				deptId			dept_id
				detpName		dept_name
		 -->
		 <!-- 鉴别器 -->
	<discriminator javaType="java.lang.String" column="dept_name">
		 <case value="AA" resultType="com.wanbangee.entities.Dept">
			<collection property="emps" column="{deptId=dept_id,deptName=dept_name}" 
				select="com.wanbangee.dao.EmpDaoPlus.selectEmpByDeptId" fetchType="lazy">
			</collection>
		 </case>
	</discriminator>
</resultMap>

	@Test
	public void testSelectAllDept() throws IOException {
		DeptDaoPlus deptDao = this.sqlSession.getMapper(DeptDaoPlus.class);
		List<Dept> depts = deptDao.selectAllDept();
//		System.out.println(depts);
		System.out.println(depts.get(0).getEmps());
		
		System.out.println(depts.get(1).getEmps());
		System.out.println(depts.get(2).getEmps());
		System.out.println(depts.get(3).getEmps());
		System.out.println(depts.get(4).getEmps());
	}

总结

映射文件指导着MyBatis如何进行数据库的增删改查,所有具有非常重要的意义,映射文件中有哪些标签:
一级标签:<mapper namespace=”对应接口的全类名”></mapper>,表示我们的这个SQL映射文件是对应哪个接口的。
也就意味着一个接口就存在一个sql映射文件。
<mapper namespace="com.wanbangee.dao.EmpDao">
二级标签:
- cache:配置二级缓存的标签【讲解MyBatis缓存策略的时候讲解】
- cache-ref:引用其他命名空间的二级缓存配置
- resultMap:自定义映射结果集
- sql : 抽取可重用的sql片段
- insert :表示新增操作
- delete:表示删除操作
- update:表示修改操作
- select:表示查询操作
- parameterMap:已废弃的参数映射
三级标签:
Where
If
Foreach
Set
trim
......
对于这一章节的内容,非常重要,我们必须完全掌握。
· 在新增数据的时候对于获取自增主键和非自增主键的值非常关键
·查询时参数的值
·查询结果的封装,有List和Map两种集合的效果
·resultMap可以实现高级的封装,封装查询的返回结果,也可以自定义封装查询结果
·resultMap可以处理多对一或者一对多的关系:
关联查询
分步查询
·延迟加载在开发中可以大大的提升数据库的查询性能
思考题:多对多的关系如何处理?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值