• CURD操作,即指的是对数据库中实体对象的增Create、改Update、查Read、删Delete操作。

1 自定义Dao接口实现类

1.1 搭建测试环境

  • 项目crud。在之前专题1的项目中加以修改

1.1.1 修改Dao接口

public interface IStudentDao {
	//插入
	void insertStudent(Student student);
	void insertStudentCatchId(Student student);
	//删改
	void deleteStudentById(int id);
	void updateStudent(Student student);
	//查询所有
	List<Student> selectAllStudents();
	Map<String, Student> selectStudentMap();
	//查询指定学生
	Student selectStudentById(int id);
	Student selectStudentByMap(Map<String, Object> map);
	//根据姓名查询
	List<Student> selectStudentsByName(String name);
}

1.1.2 修改Dao实现类

  • Dao实现类除了已经实现的insertStudent()方法之外,其他方法暂时先以空方法体的方式实现。而具体的实现后面详细来讲。
public class StudentDaoImpl implements IStudentDao{
	private SqlSession session;
	@Override
	public void insertStudent(Student student) {
		try {
			//使用工具类获取SqlSession对象
			session = MyBatisUtils.getSqlSession();
			session.insert("insertStu", student);
			session.commit();
		} finally {
			// 6.SqlSession关闭
			if(session != null) {
				session.close();
			}
		}	
	}
	@Override
	public void insertStudentCatchId(Student student) {
	}
}
	//...

1.1.3 修改测试类

  • 对于CURD的测试,使用Junit进行测试。每个方法在测试前,首先需要创建和获取Dao对象。所以,将Dao的创建放在了@Before注解的方法中。
public class Mytest {
	private IStudentDao dao;
	@Before
	public void setUp() {
		dao = new StudentDaoImpl();
	}
}

1.2 单纯插入数据

1.2.1 修改映射文件

	<insert id="insertStu" resultType="com.eason.mybatis.beans.Student">
		insert into t_student(name , age, score) values(#{name}, #{age}, #{score})
	</insert>
  • id:该SQL语句的唯一标识,Java代码中要使用该标识。
  • #{}:对于指定参数类型属性值的引用。其底层是通过反射机制,调用Student类相关属性的get方法来获取值的。因为底层使用的是反射,所以这里使用的是类的属性名,而非表的字段名。

1.2.2 修改Dao实现类

  • 使用SqlSession对象的insert()方法。该方法默认返回DB中受影响的条数。其方法原型为:insert(String id, Object obj)。
	@Override
	public void insertStudent(Student student) {
		try {
			//使用工具类获取SqlSession对象
			session = MyBatisUtils.getSqlSession();
			session.insert("insertStu", student);
			session.commit();
		} finally {
			// 6.SqlSession关闭
			if(session != null) {
				session.close();
			}
		}	
	}
  • 需要注意的是,执行完成对DB的修改操作,必须要做SqlSession的提交。否则,修改将无法同步到DB中。因为使用无参的openSession()方法已经将事务的自动提交功能给关闭。

1.2.3 修改测试类

  • 由于后面要讲解查询,所以这里要先通过插入来创建一些基本的测试数据。
	@Test
	public void testInsert() {
		for(int i = 1; i <= 10; i++) {
			Student student = new Student("n_" + i, 15 + i, 85.5 + i);
			dao.insertStudent(student);
		}
	}

1.3 插入后用新id初始化被插入对象

1.3.1 修改映射文件

  • MySql中在插入语句后执行如下语句,则会输出新插入记录的id。
insert into t_student(name, age, score) values("王五", 25, 95.5);
select last_insert_id();
insert into t_student(name, age, score) values("王五", 25, 95.5);
select @@identity;
  • 映射文件的<insert/>标签中,有一个子标签<selectKey/>用于获取新插入记录的主键值。以下两种写法均可以完成“使用新插入记录的主键值初始化被插入的对象”的功能。
	<insert id="insertStudentCatchId">
		insert into t_student(name , age, score) values(#{name}, #{age}, #{score})
		<selectKey resultType="int" keyProperty="id" order="AFTER">
			select @@identity
		</selectKey>
	</insert>
	<insert id="insertStudentCatchId">
		insert into t_student(name , age, score) values(#{name}, #{age}, #{score})
		<selectKey resultType="int" keyProperty="id" order="AFTER">
			select last_insert_id();
		</selectKey>
	</insert>
  • resultType:指出获取的主键的类型;
  • keyProperty:指出主键在Java类中对应的属性名。此处会将获取的主键值直接封装到被插入的Student对象中,即dao中insert()方法的第二个参数对象中。
  • order:指出id的生成相对于insert语句的执行是在前还是在后。MySql数据表中的id,均是先执行insert语句,而后生成id,所以需要设置为AFTER;Oracle数据库表中的id,则是在insert执行之前先生成的,所以需要设置为BEFORE。当前的MyBatis版本,不指定order属性,则会根据所用DBMS,自动选择其值。(我使用的是mybatis-3.4.6版本)

1.3.2 修改Dao实现类

	@Override
	public void insertStudentCatchId(Student student) {
		try {
			//使用工具类获取SqlSession
			session = MyBatisUtils.getSqlSession();
			session.insert("insertStudentCatchId", student);
			session.commit();
		} finally {
			if(session != null) {
				session.close();
			}
		}
	}

1.3.3 修改测试类

	//插入后用新id初始化被插入对象
	@Test
	public void testInsert() {
		Student student = new Student("张三", 23, 93.5);
		System.out.println("插入前student = " + student);
		dao.insertStudentCatchId(student);
		System.out.println("插入后student = " + student);			 
	}

1.3.4 运行结果

1.3.5 id是何时获取到的?

  • 这个新插入数据的id是何时获取到的呢?是在插入操作完成后由DB回传给Dao的实现类的吗?
  • 在Dao的实现类中insert()方法后,commit()方法前插入一条输出student对象的语句,并在commit()方法处添加一个断点。
	@Override
	public void insertStudentCatchId(Student student) {
		try {
			//使用工具类获取SqlSession
			session = MyBatisUtils.getSqlSession();
			session.insert("insertStudentCatchId", student);
			System.out.println("插入操作还未提交 student = " + student);
			//此处添加断点
			session.commit();
		} finally {
			if(session != null) {
				session.close();
			}
		}
	}
  • 以调试的方式运行,发现在插入操作还未提交时student对象已经有了id。这说明一个问题:无论插入操作是提交还是回滚,DB均会为insert的记录分配id,即使发生了回滚,这个id也已经被使用。后面再插入并提交的记录数据,此id已经不能够再使用,被分配的id是跳过此id后的id。
  • 另外,从前面<selectKey/>中order属性值的设置讲解可知,MySql在insert语句执行后会自动生成该新插入记录的主键值。主键值的生成只与insert语句是否执行有关,而与最终是否提交无关。

1.4 删除数据

1.4.1 修改映射文件

	<delete id="deleteStudentById">
		delete from t_student where id=#{xxx}
	</delete>
  • 注意,这里的动态参数id所赋值为#{xxx},这个#{xxx}表示这就是个占位符,代表delete()方法的第二个参数。#{}中可以放任何值,无需要与delete()方法的第二个参数值相同。

1.4.2 修改Dao实现类

	@Override
	public void deleteStudentById(int id) {
		try {
			//使用工具类获取SqlSession
			session = MyBatisUtils.getSqlSession();
			session.delete("deleteStudentById", id);
			session.commit();
		} finally {
			if(session != null) {
				session.close();
			}
		}
	}

1.4.3 修改测试类

	//删除数据
	@Test
	public void testDeleteStudentById() {
		dao.deleteStudentById(16);		 
	}

1.5 修改数据

1.5.1 修改映射文件

		<update id="updateStudent">
			update t_student set name=#{name}, age=#{age}, score=#{score} where id=#{id}
		</update>
  • 注意,这里的#{}中,必须要填写update()方法所传第二个参数student对象的属性名称,不能够随意填写。

1.5.2 修改Dao实现类

	@Override
	public void updateStudent(Student student) {
		try {
			//使用工具类获取SqlSession
			session = MyBatisUtils.getSqlSession();
			session.update("updateStudent", student);
			session.commit();
		} finally {
			if(session != null) {
				session.close();
			}
		}
	}

1.5.3 修改测试类

	@Test
	public void testUpdateStudent() {
		Student student = new Student("赵六", 26, 96.5);
		student.setId(18);
		dao.updateStudent(student);
	}

1.6 查询所有对象-返回List

1.6.1 修改映射文件

	<select id="selectAllStudents" resultType="com.eason.mybatis.beans.Student">
		select * from t_student
	</select>
  • 注意,resultType属性并非指的是查询结果集最后的类型,而是每查出DB中的一条记录,将该记录封装成为的对象的类型。
  • 这里resultType属性使用的是全限定性类名。对于一个映射文件来说,一般情况下是对一个类的操作均放在同一个映射文件中。所以,一个映射文件中出现的类一般是相同的。而每一个需要指定类名的地方若均需要指定全限定性类名,会比较麻烦。所以,MyBatis支持为类其别名的方式。

1.6.2 注册类的别名

  • 在主配置文件中<properties/>标签后,添加<typeAliases/>标签,指定类的别名。
  • 1、通过<typeAliases>标签指定:
	<!-- 注册类的别名 -->
	<typeAliases>
		<typeAlias type="com.eason.mybatis.beans.Student" alias="Student"/>
	</typeAliases>
  • type:全限定性类名;alias:别名。
  • 该方式的好处是:可以指定别名为简单类名以外的其他名称。当然,弊端是,必须为每个类逐个指定,比较繁琐。 2、通过<package/>指定:
	<!-- 注册类的别名 -->
	<typeAliases>
		<package name="com.eason.mybatis.beans"/>
	</typeAliases>
  • 对于实体类的全限定性类名的别名指定方式,一般使用<package/>方式,这样做的好处是会将该包中所有实体类的简单类名指定为别名,写法简单方便。
  • 但是弊端是,只能够将类的简单类名作为别名。

1.6.3 再修改映射文件

  • 此时映射文件中就可以使用类的别名:
	<select id="selectAllStudents" resultType="Student">
		select * from t_student
	</select>

1.6.4 修改Dao实现类

  • 完成Dao实现类的selectAllStudents()方法,使用SqlSession的selectList()方法完成查询操作,该方法将查询出来的每条记录封装为指定类型对象后,再将最后的结果集封装为List返回。方法原型为:List selectList(String statement)。
  • statement:映射文件中配置的SQL语句的id。
	@Override
	public List<Student> selectAllStudents() {
		List<Student> students = null;
		try {
			session = MyBatisUtils.getSqlSession();
			students = session.selectList("selectAllStudents");
		} finally {
			if(session != null) {
				session.close();
			}
		}
		return students;
	}
  • 在写查询时,由于不是对DB中数据进行的修改,所以无需通过SqlSession的提交。但是最终SqlSession对象还是需要关闭的。

1.6.5 修改测试类

	@Test
	public void testSelectAllStudents() {
		List<Student> students = dao.selectAllStudents();
		for(Student student : students) {
			System.out.println(student);
		}
	}

1.7 查询所有对象-返回Map

1.7.1 修改映射文件

  • 此例中映射文件不需要修改

1.7.2 修改Dao实现类

  • 完成Dao实现类的selectStudentMap()方法。使用SqlSession的selectMap()方法完成查询操作。该查询方法会将查询出的每条记录先封装成指定对象,然后再将该对象作为value,将该对象的指定属性所对应的字段名作为key封装为一个Map对象。方法原型为:Map<Object, Object> selectMap(String statement, String mapKey)。
  • statement:映射文件中配置的SQL语句的id;
  • mapKey:查询出的Map所要使用的key。这个key为数据表的字段名。查询出的结果是一个Map,每条记录将会对应一个Map.entry对象,该对象的key为指定字段的值,value为记录数据所封装的对象。
	@Override
	public Map<String, Student> selectStudentMap() {
		Map<String, Student> students = null;
		try {
			session = MyBatisUtils.getSqlSession();
			students = session.selectMap("selectAllStudents", "name");
		} finally {
			if(session != null) {
				session.close();
			}
		}
		return students;
	}

1.7.3 修改测试类

	@Test
	public void testSelectStudentMap() {
		Map<String, Student> map = dao.selectStudentMap();
		Student student = map.get("赵六");
		System.out.println(student);
	}

1.7.4 说明

  • 若指定的作为key的属性值在DB中并不唯一,则后面的记录值会覆盖掉前面的值。即指定可以的value值,一定是DB中该同名属性的最后一条记录值。

1.8 查询单个对象

1.8.1 修改映射文件

	<select id="selectStudentById" resultType="Student">
		select * from student where id=#{jjj}
	</select>

1.8.2 修改Dao实现类

  • 使用SqlSession的selectOne()方法。其会将查询的结果记录封装为一个指定类型的对象。方法原型为:Object selectOne(String statement, Object parameter)
  • statement:映射文件中配置的SQL语句的id。
  • parameter:查询条件中动态参数的值。
	@Override
	public Student selectStudentById(int id) {
		Student student = null;
		try {
			session = MyBatisUtils.getSqlSession();
			student = session.selectOne("selectStudentById", id);
		} finally {
			if(session != null) {
				session.close();
			}
		}
		return student;
	}

1.8.3 修改测试类

	@Test
	public void testSelectStudentById() {
		Student student = dao.selectStudentById(18);
		System.out.println(student);
	}

1.9 模糊查询

1.9.1 修改映射文件

	<select id="selectStudentsByName" resultType="Student">
		select * from t_student where name like concat('%', #{ooo}, '%')
	</select>
  • 在进行模糊查询时,需要进行字符串的拼接。SQL中的字符串的拼接使用的是函数concat(arg1, arg2, ...)。注意不能使用Java中的字符串连接符+。
  • 当然,也可以写为如下形式:
	<select id="selectStudentsByName2" resultType="Student">
		select * from t_student where name like "%" #{ooo} "%"
	</select>
  • 以上两种形式是等效的,都是以动态参数的形式出现在SQL语句中的。
  • 还可以使用如下形式,只是需要注意,使用${}中的只能是value,不能使用其他。
	<select id="selectStudentsByName3" resultType="Student">
		select * from t_student where name like "%${value}%"
	</select>
  • 这种方式是纯粹的字符串拼接,直接将参数拼接到了SQL语句中。这种方式可能会发生SQL注入。

1.9.2 修改Dao实现类

	@Override
	public List<Student> selectStudentsByName(String name) {
		List<Student> students = null;
		try {
			session = MyBatisUtils.getSqlSession();
			students = session.selectList("selectStudentsByName", name);
		} finally {
			if(session != null) {
				session.close();
			}
		}
		return students;
	}

1.9.3 修改测试类

	@Test
	public void testSelectStudentsByName() {
		List<Student> students = dao.selectStudentsByName("张三");
		for(Student student : students) {
			System.out.println(student);
		}
	}

1.9.4 $和#的区别

  • $和#的区别是很大的。#为占位符,而$为字符串拼接符。
  • 字符串拼接是将参数值以硬编码的方式直接拼接到了SQL语句中,字符串拼接就会引发两个问题:SQL注入问题与没有使用预编译所导致的执行效率低下问题。
  • 一般情况下,动态参数的值是由用户输入的,则不能使用拼接符$,因为有可能会出现SQL注入;但是动态参数的值是由系统计算生成的,则可以使用拼接符$。但是这样虽然不存在SQL注入的风险,仍然存在执行效率问题。

1.9.5 回顾:SQL注入问题

  • 例如,有一个用于根据姓名进行查询的表单。
  • 其后台SQL语句为拼接字符串:String sql = "select * from t_student where name like '%" + name + " %''";
  • 若用户正常输入,则查询出所有“张”姓学生是没有问题的,执行的sql语句为:select * from t_student where name like '%张%';
  • 但是,若用户有意进行SQL注入,则会输入如下内容:
  • 此时表单获取的属性值,即name值为:张%' or 1=1 or '。与后天的sql语句拼接在一块,则形成如下的sql:select * from t_student where name like '%张%' or 1=1 or '%'。
  • 此时SQL的执行结果为,查询出所有数据记录。
  • 若在进行查询时,使用的为PreparedStatement,而非Statement,则可防止SQL注入的产生。
  • 在MyBatis中,使用#号为占位符,则后台执行SQL使用的为PreparedStatement,将会防止SQL注入;而使用$符,则为字符串拼接,使用的是Statement,将无法防止SQL注入。

1.9.6 回顾:SQL的预编译问题

  • 当Java代码通过JDBC的Statement向DB中发送一条SQL语句时,DBMS会对SQL语句编译后执行。
  • 当Java代码通过JDBC的PreparedStatement向DB中发送一条SQL语句时,DBMS会首先编译SQL语句,然后将编译好的SQL放入DBMS的数据库缓存池中再执行。当DBMS再次接收到该数据库操作的SQL时,先从DB缓存池中查找该语句是否被编译过,若被编译过,则直接执行,否则先编译后将编译结果放入DB缓存池中,再执行。

1.10 根据Map进行查询

  • mapper中SQL语句的动态参数也可以是Map的key。

1.10.1 修改测试类

	@Test
	public void testSelectStudentByMap() {
		Student student = new Student();
		student.setId(23);
		
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("studentId", 18);
		map.put("student", student);
		
		student = dao.selectStudentByMap(map);
		System.out.println(student);
		
	}

1.10.2 修改映射文件

	<select id="selectStudentByMap" resultType="Student">
		select * from t_student where id=#{studentId}
	</select>
	<select id="selectStudentByMap2" resultType="Student">
		select * from t_student where id=#{student.id}
	</select>

1.10.3 修改Dao实现类

	@Override
	public Student selectStudentByMap(Map<String, Object> map) {
		Student student = null;
		try {
			session = MyBatisUtils.getSqlSession();
			student = session.selectOne("selectStudentByMap2", map);
		} finally {
			if(session != null) {
				session.close();
			}
		}
		return student;
	}

2 属性名与查询字段名不相同

  • resultType可以将查询结果直接映射为实体Bean对象的条件是,SQL查询的字段名与实体Bean的属性名一致。因为在将查询结果转换为指定类型对象时,系统自动将查询结果字段名称作为对象的属性名,通过反射机制完成对象的创建。
  • 当SQL查询结果的字段名与实体Bean的属性名不一致时,将无法创建出需要类型的对象。此时有两种解决方案。

2.1 搭建测试环境

2.1.1 修改student表

2.1.2 修改Dao接口

public interface IStudentDao {
	Student selectStudentById(int id);
}

2.1.3 修改Dao接口

	@Test
	public void testSelectStudentById() {	 
		dao = new StudentDaoImpl();
		Student student = dao.selectStudentById(3);
		System.out.println(student);
	}

2.1.4 定义Dao实现类

	@Override
	public Student selectStudentById(int id) {
		Student student = null;
		try {
			session = MyBatisUtils.getSqlSession();
			student = session.selectOne("selectStudentById", id);		
		} finally {
			if(session != null){
				session.close();
			}
		}
		return student;
	}

2.2 查询字段使用别名

  • 虽然属性名称与表中字段名称不一致,但是可以为查询结果的字段名赋予别名,让别名与实体Bean的属性名相同。这样框架也就可以根据查询结果利用反射机制将对象创建。
  • 在映射文件中mapper中添加如下映射:
	<select id="selectStudentById" resultType="Student">
		select t_id id, t_name name, t_age age, t_score score from t_student where t_id =#{###}
	</select>

2.3 使用结果映射resultMap

  • 可以使用结果映射resultMap(这里的Map是映射mapper的意思)来建立映射关系,完成由字段到属性的映射,达到将查询结果封装为对象的目的。resultMap是对resultType的增强。
  • 直接修改映射文件mapper.xml:
	<resultMap type="Student" id="studentMapper">
		<id column="t_id" property="id"/>
		<result column="t_name" property="name"/>
		<result column="t_age" property="age"/>
		<result column="t_score" property="score"/>
	</resultMap>
	<select id="selectStudentById" resultMap="studentMapper">
		select *  from t_student where t_id =#{ooo}
	</select>
  • <resultMap/>标签中定义了由type指定的类的属性名到表中字段名称的映射关系。根据这个映射关系,框架利用反射机制创建相应对象。 1、type:指定要映射的实体类; 2、id:指定该resultMap映射关系的名称; 3、<id/>标签:id的字段名column与实体类的属性property间的映射关系; 4、<result/>标签:id以外其他字段名column与实体类的属性property间的映射关系;
  • 当然,对于字段名与实体类的属性名相同的情况下,可以不写入<resultMap/>标签中。

3 Mapper动态代理

  • 在前面例子中自定义Dao接口实现类时发现一个问题:Dao的实现类其实并没有干什么实质性的工作,它仅仅就是通过SqlSession的相关API定位到映射文件mapper中相应id的SQL语句,真正对DB进行操作的工作其实是由框架通过mapper中的SQL完成的。
  • 所以,MyBatis框架就抛开了Dao的实现类,直接定位到映射文件mapper中的相应SQL语句,对DB进行操作。这种对Dao的实现方式称之为Mapper的动态代理方式。
  • Mapper动态代理方式无需程序员实现Dao接口。接口是由MyBatis结合映射文件自动生成的动态代理实现的。

3.1 映射文件的namespace属性值

  • 一般情况下,一个Dao接口的实现类方法使用的是同一个SQL映射文件中的SQL映射id。所以,MyBatis框架要求,将映射文件中<mapper/>标签的namespace属性设为Dao接口的全类名,则系统会根据方法所属Dao接口,自动到相应namespace的映射文件中查找相关的SQL映射。
  • 简单来说,通过接口名即可定位到映射文件mapper。
<mapper namespace="com.eason.mybatis.dao.IStudentDao">
</mapper>

3.2 Dao接口方法名

  • MyBatis框架要求,接口中的方法名,与映射文件中相应的SQL标签的id值相同。系统会自动根据方法名到相应的映射文件中查找同名的SQL映射id。
  • 简单来说,通过方法名就可以定位到映射文件mapper中的相应的SQL语句。

3.3 Dao对象的获取

  • 使用时,只需要使用SqlSession中的getMapper()方法,即可获取指定接口的实现类对象。该方法的参数为指定Dao接口类的class值。

3.4 删除Dao实现类

  • 由于通过调用Dao接口的方法,不仅可以从SQL映射文件中找到所要执行的SQL语句,还可以通过方法参数以及返回值,将SQL语句的动态参数传入,将查询结果返回。所以,Dao的实现工作,完全可以由MyBatis系统自动根据映射文件完成。所以,Dao的实现类就不再需要了。
  • Dao实现对象是由JDK的Proxy动态代理自动生成的。

3.5 修改测试类

3.5.1 @Before与@After注解方法

  • 在@Before注解方法中获取到SqlSession对象后,通过SqlSession的getMapper()方法创建Dao接口实现类的动态代理对象。
  • 在@After注解方法中关闭SqlSession对象。
	public class Mytest {
		private IStudentDao dao;
		private SqlSession session;
		@Before
		public void setUp() {
			session = MyBatisUtils.getSqlSession();
			dao = session.getMapper(IStudentDao.class);
		}
		@After
		public void tearDown() {
			if(session != null) {
				session.close();
			}
		}
	}

3.5.2 修改mapper映射文件

		<insert id="insertStudent" >
			 insert into t_student(t_name , t_age, t_score) values(#{name}, #{age}, #{score})
		</insert>
		<insert id="insertStudentCatchId">
				insert into t_student(t_name , t_age, t_score) values(#{name}, #{age}, #{score})
				<selectKey resultType="int" keyProperty="id" order="AFTER">
						select @@identity
				</selectKey>
		</insert>
		<delete id="deleteStudentById">
				delete from t_student where t_id=#{xxx}
		</delete>

3.5.3 添加SqlSession的提交方法

  • 在增删改测试方法的最后,添加SqlSession的commit()方法,完成提交。
@Test
	public void test01() {	 
		for (int i = 1; i < 10; i++) {
			Student student = new Student("n_1" + i, 15 + i, 85.5 + 1);
			dao.insertStudent(student);
		}
		session.commit();
	}
	@Test
	public void test02() {	 
		Student student = new Student("李四", 24, 98);
		System.out.println("student = " + student);
		dao.insertStudentCatchId(student);
		System.out.println("student = " + student);
		session.commit();
	}
	@Test
	public void test03() {
		dao.deleteStudentById(45);
		session.commit();
	}

3.5.4 删除selectStudentMap()方法测试

  • MyBatis框架对于Dao查询的自动实现,底层只会调用selectOne()和selectList()方法。而框架选择方法的标准是测试类中用于接收返回值的对象类型。若接收类型为List,则自动选择selectList方法;否则,自动选择selectOne()方法。
  • 这里接收类型为Map,所以框架选择了selectOne()方法,会报错。所以这里需要删除这个selectStudentMap()方法的测试。

3.6 多查询条件无法整体接收问题的解决

  • 在实际工作中,表单中所给出的查询条件有时是无法将其封装为一个对象的,也就是说,查询方法只能够携带多个参数,而不能够携带这多个参数进行封装的一个对象。对于这个问题,有两种解决方案。
  • 可以使用将这多个参数封装为一个Map方式实现。即将这多个参数封装为一个Map<String, Object>,根据Map进行查询。 1、修改Dao接口:在Dao接口中添加如下方法:
List<Student> selectStudentByMap(Map<String, Object> map);

2、修改测试类:

	public void test05() {
		Map<String, Object> map = new HashMap<String, Object>(); 
		map.put("nameCondition", "李");
		map.put("ageCondition", 20);
		List<Student> students = dao.selectStudentByMap(map);
		for(Student student : students) {
			System.out.println(student);
		}
	}

3、修改映射文件:

		<resultMap type="Student" id="studentMapper">
			<id column="t_id" property="id"/>
			<result column="t_name" property="name"/>
			<result column="t_age" property="age"/>
			<result column="t_score" property="score"/>
		</resultMap>
		<select id="selectStudentByMap" resultMap="studentMapper">
			select * from t_student where t_name like "%" #{nameCondition} "%" and t_age > #{ageCondition}
		</select>

4 动态SQL

  • 动态SQL,主要用于解决查询条件不确定的情况:在程序运行期间,根据用户的查询条件进行查询。提交的查询条件不同,执行的SQL语句不同。若将每种可能的情况均逐一列出,对所有条件进行排序组合,将会出现大量的SQL语句不同。此时,可使用动态SQL来解决这样的问题。
  • 动态SQL,即通过MyBatis提供的各种标签对条件作出判断以实现动态拼接SQL语句。
  • 这里的条件判断使用的表达式为OGNL表达式。常用的动态SQL标签有<if>、<where>、<choose>、<foreach>等。
  • 有一个有意思的发现是,MyBatis的动态SQL语句,与JSTL中的语句非常相似。

4.1 测试环境的搭建

4.1.1 定义数据库表

4.1.2 定义实体

public class Student {
	private Integer id;
	private String name;
	private int age;
	private double score;

	//无参构造器和带参构造器
	//getter and setter
	//toString()
}

4.1.3 定义测试类

  • 测试类暂时只定义@Before与@After方法,其他测试方法后面再逐步填充。
public class Mytest {
	private IStudentDao dao;
	private SqlSession session;
	
	@Before
	public void setUp() {
		session = MyBatisUtils.getSqlSession();
		dao = session.getMapper(IStudentDao.class);
	}
	
	@After
	public void tearDown() {
		if(session != null) {
			session.close();
		}
	}	
}

4.1.4 注意事项

  • 在mapper的动态SQL中若出现大于号(>)、小于号(<)、大于等于号(>=),小于等于号(<=)等符号,最好将其转换成实体符号,否则,XML可能会出现解析出错问题。
  • 特别是对于小于号(<),在XML中是绝对不能够出现的。否则,一定会出错。

4.2 < if/>标签

  • 对于该标签的执行,当test的值为true时,会将其包含的SQL片段拼接到其所在的SQL语句中。
  • 本例实现的功能是:查询出满足用户提交查询条件的所有学生。用户提交的查询条件可以包含一个姓名的模糊查询,同时还可以包含一个年龄的下限。当然,用户在提交表单时可能两个条件均做出了设定,也可能两个条件均不作设定,也可以只做其中一项设定。
  • 这引发的问题是,查询条件不确定,查询条件依赖于用户提交的内容。此时,就可以使用动态SQL语句,根据用户提交内容对将要执行的SQL进行拼接。

4.2.1 定义Dao接口

public interface IStudentDao {
	List<Student> selectStudentsIf(Student student);
}

4.2.2 定义映射文件

  • 为了解决两个条件均未做设定的情况,在where后添加了一个“1=1”的条件。这样就不至于两个条件均未设定而出现只剩下一个where,而没有任何拼接的条件的不完整SQL语句。
<mapper namespace="com.eason.mybatis.dao.IStudentDao">
	<!-- if -->
	<select id="selectStudentsIf" resultType="Student">
		select * from student where 1=1 
		<if test="name != null and name != ''">
			and name like "%" #{name} "%"
		</if>
		<if test="age > 0">
			and age > #{age}
		</if>
	</select>
</mapper>

4.2.3 修改测试类

	@Test
	public void test01() {
		//Student student = new Student();
		//Student student = new Student("", 25, 0);
		//Student student = new Student("李", -22, 0);
		Student student = new Student("李", 22, 0);		
		
		List<Student> students = dao.selectStudentsIf(student);
		System.out.println(students);
	}

4.3 < where/>标签

  • <if/>标签中存在一个比较麻烦的地方:需要在where后手工添加1=1的子句。因为,若where后的所有<if/>条件均为false,而where后若有没有1=1子句,则SQL中就会只剩下一个空的where,SQL出错。所以,在where后,需要添加永远为真子句1=1,以防止这种情况的发生,但是当数据量很大时,会严重影响查询效率。

4.3.1 修改Dao接口

List<Student> selectStudentsWhere(Student student);	

4.3.2 修改映射文件

  • 使用<where/>标签,在有查询条件时,可以自动添加上where子句;没有查询条件时,不会添加where子句。需要注意的是,第一个<if/>标签中的SQL片段,可以不包含and。不过,写上and也没错,系统会自动将多出来的and去掉。但是其他<if/>中SQL片段的and,必须要求写上,否则SQL语句将拼接出错。
	<select id="selectStudentsWhere" resultType="Student">
		select * from t_student 
		<where>
			<if test="name != null and name != ''">
				and name like "%" #{name} "%"
			</if>
			<if test="age > 0">
				and age > #{age}
			</if>
		</where>
	</select>

4.3.3 修改测试类

	public void test02() {
		Student student = new Student();
		//Student student = new Student("", 25, 0);
		//Student student = new Student("李", -22, 0);
		//Student student = new Student("李", 22, 0);		
		
		List<Student> students = dao.selectStudentsWhere(student);
		System.out.println(students);
	}

4.4 < choose/>标签

  • 该标签中只可以包含<when/><otherwise/>,可以包含多个<when/>与一个<otherwise/>它们联合使用,完成Java中的开关语句switch...case功能。
  • 本例要完成的需求是,若姓名不空,则按照姓名查询:若姓名为空,则按照年龄查询;若没有查询条件,则没有查询结果。

4.4.1 修改Dao接口

List<Student> selectStudentsChoose(Student student);	

4.4.2 修改映射文件

  • 对于<choose/>标签,其会从第一个<when/>开始逐个向后进行条件判断。若出现<when/>中的test属性值为true的情况,则直接结果<choose/>标签,不再向后进行判断查找。若所有<when/>的test判断结果均为false,则最后会执行<otherwise/>标签。
	<select id="selectStudentsChoose" resultType="Student">
		select * from t_student
		<where>
			<choose>
				<when test="name != null and name != ''">
					and name like "%" #{name} "%"
				</when>
				<when test="age > 0">
					and age < #{age}
				</when>
				<otherwise>
					and 1 != 1
				</otherwise>
			</choose>
		</where>
	</select>

4.4.3 修改测试类

	@Test
	public void test03() {
		//Student student = new Student();
		Student student = new Student("", 25, 0);
		//Student student = new Student("刘", 0, 0);	
		
		List<Student> students = dao.selectStudentsChoose(student);
		System.out.println(students);
	}

4.5 <foreach/>标签--遍历数组

  • <foreach/>标签用于实现对于数组和集合的遍历。对其使用,需要注意: 1、collection表示要遍历的集合类型。这里有数组,即array; 2、open、close、separator为对遍历内容的SQL拼接;
  • 本例实现的需求是:查询出id为1和3的学生的信息。

4.5.1 修改Dao接口

List<Student> selectStudentsForeachArray(Object[] studentIds);	

4.5.2 修改映射文件

  • 动态SQL的判断中使用的都是OGNL表达式。OGNL表达式中的数组使用array表示,数组长度使用array.length表示。
	<select id="selectStudentsForeachArray" resultType="Student">
		<!-- select * from t_student where id in (1,3) -->
		select * from t_student 
		<if test="array != null and array.length > 0">
			where id in 
			<foreach collection="array" open="(" close=")" item="myid" separator=",">
				#{myid}
			</foreach>
		</if>
	</select>

4.5.3 修改测试类

	@Test
	public void test04() {
		Object[] studentIds = new Object[] {1, 3};
		List<Student> students = dao.selectStudentsForeachArray(studentIds);
		System.out.println(students);
	}

4.6 <foreach/>标签--遍历泛型为基本类型的List

  • 本例实现的需求是,查询出id为1与3的学生信息。

4.6.1 修改Dao接口

List<Student> selectStudentsForeachList(List<Integer> studentIds);	

4.6.2 修改映射文件

  • OGNL表达式中的List使用list表示,其大小使用list.size表示。
	<select id="selectStudentsForeachList" resultType="Student">
		<!-- select * from student where id in (1,3) -->
		selct * from t_student 
		<if test="list != null and list.size > 0">
			where id in 
			<foreach collection="list" open="("  close=")" item="myid" separator=",">
				#{myid}
			</foreach>
		</if>
	</select>

4.6.3 修改测试类

	@Test
	public void test05() {
		List<Integer> studentIds = new ArrayList<Integer>();
		studentIds.add(1);
		studentIds.add(3);
		
		List<Student> students = dao.selectStudentsForeachList(studentIds);
		System.out.println(students);
	}

4.7 < sql/>标签

  • <sql/>标签用于定义SQL片段,以便其他SQL标签复用。而其他标签使用该SQL片段,需要使用<include/>子标签。该<sql/>标签可以定义SQL语句中的任何部分,所以<include/>子标签可以放在动态SQL的任何位置。

4.7.1 修改Dao接口

List<Student> selectStudentsBySQLFragment(List<Integer> studentIds);	

4.7.2 修改映射文件

	<!-- 定义SQL片段 -->
	<sql id="selectHead">
		select * from t_student
	</sql>
		<select id="selectStudentsBySQLFragment" resultType="Student">
		<!-- 使用SQL片段 -->
		<include refid="selectHead"></include>
		<if test="list != null and list.size > 0">
			where id in 
			<foreach collection="list" open="("  close=")" item="myid" separator=",">
				#{myid}
			</foreach>
		</if>
	</select>

4.7.3 修改测试类

	@Test
	public void test06() {
		List<Integer> studentIds = new ArrayList<Integer>();
		studentIds.add(1);
		studentIds.add(3);
		
		List<Student> students = dao.selectStudentsBySQLFragment(studentIds);
		System.out.println(students);
	}