单表的CURD操作(使用mapper动态代理)
MyBatis框架抛开dao的实现类,直接定位到映射文件mapper的相应sql语句,对DB进行操作。这种dao的实现方式成为mapper的动态代理方式。
mapper动态代理方式无需程序员实现dao接口。接口是由MyBatis结合映射文件自动生成的动态代理实现的。
1.映射文件的namespace属性值
一般情况下,一个dao接口的实现类方法使用的是同一个sql映射文件中的sql映射id。所以,MyBatis框架要求,将映射文件中<mapper/>标签的namespace属性设为dao接口的全类名,则系统会根据方法所属dao接口,自动到相应namespace的映射文件中查找相关的sql映射。
简单来说,通过接口名即可定位到映射文件mapper。
2.修改日志输出控制文件
mapper的namespace修改了,则需要将日志输出控制文件中的logger的输出对象进行修改
##define a logger
#log4j.logger.namespace_value=trace,console
log4j.logger.com.hcx.dao.IStudentDao=trace,console
3.dao接口方法名
MyBatis框架要求,接口中的方法名,与映射文件中相应的sql标签的id值相同。系统会自动根据方法名到相应的映射文件中查找同名的sql映射id。
简单来说,通过方法名就可定位到映射文件mapper中相应的sql语句。
接口:
public interface IStudentDao {
int insertStudent(Student student);
void deleteStudentById(int id);
void updateStudent(Student student);
List<Student> selectAllStudents();
Map<String, Object> selectAllStudentsMap();
Student selectStudentById(int id);
List<Student> selectStudentsByName(String name);
}
映射:
<select id="selectStudnetById" parameterType="int" resultType="com.hcx.beans.Student">
select id,name,age,score,birthday from student where id=#{id}
</select>
4.dao对象的获取
使用时,只需要调用Sqlsession的getMapper()方法,即可获取指定接口的实现类对象。该方法的参数为指定dao接口类的class值。
session = factory.openSession();
dao = session.getMapper(IStudentDao.class);
5.删除dao实现类
由于通过调用dao接口的方法,不仅可以从sql映射文件中找到所要执行sql语句,还可通过方法参数及返回值,将sql语句的动态参数传入,将查询结果返回。所以,dao的实现工作,完全可以由MyBatis系统自动根据映射文件完成。所以,dao的实现类就不再需要了。
dao实现对象是由jdk的proxy动态代理自动生成的。
6.测试类
1.在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();
}
}
}
2.添加Sqlsession的提交方法
在增删改测试方法的最后,添加Sqlsession的commit方法,完成提交。
@Test
public void test01(){
Student student = new Student("张三",23,99.8);
dao.insertStudent(student);
session.commit();
}
@Test
public void test02(){
Student student = new Student("张三",23,99.8);
dao.insertStudentCatchId(student);
System.out.println("student="+student);
session.commit();
}
@Test
public void test03(){
dao.deleteStudentById(3);
session.commit();
}
3.删除selectStudentMap()方法测试
MyBatis框架对于dao查询的自动实现,底层只会调用selectOne与selectList()方法。而框架选择方法的标准是测试类中用户接收返回值得对象类型。若接收类型为list,则自动选择selectList()方法;否则,自动选择selectOne()方法。
接收类型为map,所以框架选择了selectOne()方法,会报错。
7.多查询条件无法整体接收问题的解决
在实际工作中,表单中所给出的查询条件有时是无法将其封装为一个对象的,也就是说,查询方法只能携带多个参数,而不能携带将着这多个参数进行封装的一个对象。对于这个问题,有两种解决方案。
方案一:将这多个参数封装为一个map
将这多个参数封装为一个Map<String,Object>,根据Map进行查询。
1.dao接口
List<Student> selectStudentByMap(Map<String, Object> map);
2.测试类
@Test
public void test10(){
Map<String, Object> map = new HashMap<String,Object>();
map.put("nameCondition", "张");
map.put("ageCondition",22);
List<Student> students = dao.selectStudentByMap(map);
for (Student student : students) {
System.out.println(student);
}
}
3.修改映射文件
<select id="selectStudentByMap" resultType="Student">
select *from student
where name like '%' #{nameCondition} '%'
and age > #{ageCondition}
</select>
方案二:多个参数逐个接收
对于mapper中的SQL语句,可以通过参数索引#{index}的方式逐个接收每个参数。
1.dao接口
List<Student> selectStudentByConditions(String name,int age);
2.测试类
@Test
public void test11(){
List<Student> students = dao.selectStudentByConditions("张", 22);
for (Student student : students) {
System.out.println(student);
}
}
3.映射文件
<select id="selectStudentByconditions" resultType="Student">
select * from student
where name like '%' #{0} '%'
and age > #{1}
</select>
动态SQL
动态sql,主要用于解决查询条件不确定的情况:在程序运行期间,根据用户提交的查询条件进行查询。提交的查询条件不同,执行的sql语句不同。若将每种可能的情况均逐一列出,对所有条件进行排列组合,将会出现大量的sql语句。此时,可使用动态sql来解决这样的问题
动态sql,即通过MyBatis提供的各种标签对条件作出判断以实现拼接sql语句
这里的条件判断使用的表达式为OGNL表达式。常用的动态SQL标签有<if>、<where>、<choose/>、<foreach>等。
1.实体类:
public class Student {
private Integer id;
private String name;
private int age;
private double score;
//无参构造器与带参构造器
//getter 和 setter
//toString()
}
2.测试类
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();
}
}
}
注意事项:
在mapper的动态sql中若出现大于号(>)、小于号(<)、大于等于号(>=)、小于等于号(<=)等符号,最好将其转换为实体符号。否则,xml可能会出现解析出错问题。
1.<if/>标签
对于该标签的执行,当test的值为true时,会将其包含的sql片段拼接到其后所在的sql语句中。
例:查询出满足用户提交查询条件的所有学生。用户提交的查询条件可以包含一个姓名的模糊查询,同时还可以包含一个年龄的下限。当然,用户在提交表单时可能两个条件均作出了设定,也可能两个条件均不做设定,也可以只做其中一项设定。
查询条件不确定,查询条件依赖于用户提交的内容。此时,就可使用动态sql语句,根据用户提交内容对将要执行的sql进行拼接。
dao接口:
public interface IStudentDao {
List<Student> selectStudentsIf(Student student)
}
映射文件:
<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>
测试类:
@Test
public void test01(){
Student student = new Student();
List<Student> students = dao.selectStudentsIf(student);
System.out.println(students);
}
2.<where/>标签
dao接口:
public interface IStudentDao {
List<Student> selectStudentsIf(Student student);
List<Student> selectStudentsWhere(Student student);
}
映射文件:
<select id="selectStudentsWhere" resultType="Student">
select * from student
<where>
<if test="name!=null and name!=''">
and name like '%' #{name} '%'
</if>
<if test="age > 0">
and age > #{age}
</if>
</where>
</select>
测试:
@Test
public void test02(){
Student student = new Student();
List<Student> students = dao.selectStudentsWhere(student);
System.out.println(students);
}
3.<choose/>标签
该标签只可以包含<when/><otherwise/>,可以包含多个<when/>与一个<otherwise/>。他们联合使用,完成java中的开关语句switch..case功能。
需求:若姓名不空,则按姓名查询;若姓名为空,则按年龄查询;若没有查询条件,则没有查询结果。
dao接口:
public interface IStudentDao {
List<Student> selectStudentsIf(Student student);
List<Student> selectStudentsWhere(Student student);
List<Student> selectStudentsChoose(Student student);
}
映射文件:
对于<choose/>标签,其会从第一个<when/>开始逐个向后进行条件判断。若出现<when/>中的test属性值为true的情况,则直接结束<choose/>标签,不再向后进行判断查找。若所有<when/>的test判断结果均为false,则最后会执行<otherwise/>标签。
<select id="selectStudentsChoose" resultType="Student">
select * from 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>
测试类:
@Test
public void test03(){
Student student = new Student();
List<Student> students = dao.selectStudentsChoose(student);
System.out.println(students);
}
4.<foreach/>标签--遍历数组
<foreach/>标签用于实现对于数组于集合的遍历。对其使用,需要注意:
- collection表示要遍历的集合类型,这里是数组,即array
- open、close、separator为对遍历内容的SQL拼接
dao接口:
public interface IStudentDao {
List<Student> selectStudentsIf(Student student);
List<Student> selectStudentsWhere(Student student);
List<Student> selectStudentsChoose(Student student);
List<Student> selectStudentsForeachArray(Object[] studentIds);
}
映射文件
动态sql的判断中使用的都是OGNL表达式。OGNL表达式中的数组使用array表示,数组长度使用array.length表示。
<select id="selectStudentsForeachArray" resultType="Student">
select * from student
<if test="array !=null and array.length >0">
where id in
<foreach collection="array" open="(" close=")" item="myid" separator=",">
#{myid}
</foreach>
</if>
</select>
测试类:
@Test
public void test04(){
Object[] studentIds = new Object[]{1,3};
List<Student> students = dao.selectStudentsForeachArray(studentIds);
System.out.println(students);
}
5.<foreach/>标签--遍历泛型为基本类型的List
dao接口:
public interface IStudentDao {
List<Student> selectStudentsIf(Student student);
List<Student> selectStudentsWhere(Student student);
List<Student> selectStudentsChoose(Student student);
List<Student> selectStudentsForeachArray(Object[] studentIds);
List<Student> selectStudentsForeachList(List<Integer> studentIds);
}
映射文件:
OGNL表达式中的List使用list表示,其大小使用list.size表示。
<select id="selectStudentsForeachList" resultType="Student">
select * from student
<if test="list!=null and list.size > 0">
where id in
<foreach collection="list" open="(" close=")" item="myid" separator=",">
#{myid}
</foreach>
</if>
</select>
测试类:
@Test
public void test05(){
List<Integer> studentIds = new ArrayList<Integer>();
student.add(1);
student.add(3);
List<Student> students = dao.selectStudentsForeachList(studentIds);
System.out.println(students);
}
6.<foreach/>标签--遍历泛型为自定义类型的List
dao接口:
List<Student> selectStudentsForeachList2(List<Student> students);
映射文件:
注意,这里的当前遍历对象类型是List中的泛型,即是Student对象。
<select id="selectStudentsForeachList2" resultType="Student">
<!-- select * from student where id in(1,3) -->
select * from student
<if test="list!=null and list.size > 0">
where id in
<foreach collection="list" open="(" close=")" item="stu" separator=",">
#{stu.id}
</foreach>
</if>
</select>
测试类:
@Test
public void test09(){
Student student1 = new Student();
student1.setId(1);
Student student3 = new Student();
student3.setId(3);
List<Student> students = new ArrayList<Student>();
students.add(student1);
students.add(student3);
students = dao.selectStudentsForeachList2(students);
System.out.println(students);
}
<foreach/>标签执行insert操作:
在执行插入时要注意不能用外部的括号:
/**
* 保存品牌id和类目id到中间表
* @param bid 品牌id
* @param cidList 所属类目id集合
*/
void insertCategoryAndBrand(@Param("bid")Long bid, @Param("cidList") List cidList);
xml:
<insert id="insertCategoryAndBrand" parameterType="map">
INSERT INTO tb_category_brand(category_id,brand_id)
VALUES
<foreach collection="cidList" item="item" separator=",">
(#{item},#{bid})
</foreach>
</insert>
真正执行时的sql语句:insert into tb_category_brand(category_id,brand_id) values(1000,1000),(1000,2000);
是分别每一组数据都有括号,因为不能使用open和close标签来追加括号在最外面,否则会报以下错误:
7.<sql/>标签
<sql/>标签用于定义sql片段,以便其它sql标签复用。而其他标签使用该SQL片段,需要使用<include/>子标签。该<sql/>标签可以定义SQL语句中的任何部分,所以<include/>子标签可以放在动态sql的任何位置
dao接口:
List<Student> selectStudentsBySQLFragment(List<Student> students);
映射文件:
测试类:
@Test
public void test09(){
Student student1 = new Student();
student1.setId(1);
Student student3 = new Student();
student3.setId(3);
List<Student> students = new ArrayList<Student>();
students.add(student1);
students.add(student3);
students = dao.selectStudentsBySQLFragment(students);
System.out.println(students);
}