單表的CURD操作(使用mapper動(dòng)態(tài)代理)
MyBatis框架拋開dao的實(shí)現(xiàn)類,直接定位到映射文件mapper的相應(yīng)sql語句,對(duì)DB進(jìn)行操作。這種dao的實(shí)現(xiàn)方式成為mapper的動(dòng)態(tài)代理方式。
mapper動(dòng)態(tài)代理方式無需程序員實(shí)現(xiàn)dao接口。接口是由MyBatis結(jié)合映射文件自動(dòng)生成的動(dòng)態(tài)代理實(shí)現(xiàn)的。
1.映射文件的namespace屬性值
一般情況下,一個(gè)dao接口的實(shí)現(xiàn)類方法使用的是同一個(gè)sql映射文件中的sql映射id。所以,MyBatis框架要求,將映射文件中<mapper/>標(biāo)簽的namespace屬性設(shè)為dao接口的全類名,則系統(tǒng)會(huì)根據(jù)方法所屬dao接口,自動(dòng)到相應(yīng)namespace的映射文件中查找相關(guān)的sql映射。
簡(jiǎn)單來說,通過接口名即可定位到映射文件mapper。
2.修改日志輸出控制文件
mapper的namespace修改了,則需要將日志輸出控制文件中的logger的輸出對(duì)象進(jìn)行修改
##define a logger
#log4j.logger.namespace_value=trace,console
log4j.logger.com.hcx.dao.IStudentDao=trace,console
3.dao接口方法名
MyBatis框架要求,接口中的方法名,與映射文件中相應(yīng)的sql標(biāo)簽的id值相同。系統(tǒng)會(huì)自動(dòng)根據(jù)方法名到相應(yīng)的映射文件中查找同名的sql映射id。
簡(jiǎn)單來說,通過方法名就可定位到映射文件mapper中相應(yīng)的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對(duì)象的獲取
使用時(shí),只需要調(diào)用Sqlsession的getMapper()方法,即可獲取指定接口的實(shí)現(xiàn)類對(duì)象。該方法的參數(shù)為指定dao接口類的class值。
session = factory.openSession();
dao = session.getMapper(IStudentDao.class);
5.刪除dao實(shí)現(xiàn)類
由于通過調(diào)用dao接口的方法,不僅可以從sql映射文件中找到所要執(zhí)行sql語句,還可通過方法參數(shù)及返回值,將sql語句的動(dòng)態(tài)參數(shù)傳入,將查詢結(jié)果返回。所以,dao的實(shí)現(xiàn)工作,完全可以由MyBatis系統(tǒng)自動(dòng)根據(jù)映射文件完成。所以,dao的實(shí)現(xiàn)類就不再需要了。
dao實(shí)現(xiàn)對(duì)象是由jdk的proxy動(dòng)態(tài)代理自動(dòng)生成的。
6.測(cè)試類
1.在before注解方法中獲取到Sqlsession對(duì)象后,通過Sqlsession的getMapper方法創(chuàng)建dao接口實(shí)現(xiàn)類的動(dòng)態(tài)代理對(duì)象。在after注解方法中關(guān)閉Sqlsession對(duì)象。
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的提交方法
在增刪改測(cè)試方法的最后,添加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()方法測(cè)試
MyBatis框架對(duì)于dao查詢的自動(dòng)實(shí)現(xiàn),底層只會(huì)調(diào)用selectOne與selectList()方法。而框架選擇方法的標(biāo)準(zhǔn)是測(cè)試類中用戶接收返回值得對(duì)象類型。若接收類型為list,則自動(dòng)選擇selectList()方法;否則,自動(dòng)選擇selectOne()方法。
接收類型為map,所以框架選擇了selectOne()方法,會(huì)報(bào)錯(cuò)。
7.多查詢條件無法整體接收問題的解決
在實(shí)際工作中,表單中所給出的查詢條件有時(shí)是無法將其封裝為一個(gè)對(duì)象的,也就是說,查詢方法只能攜帶多個(gè)參數(shù),而不能攜帶將著這多個(gè)參數(shù)進(jìn)行封裝的一個(gè)對(duì)象。對(duì)于這個(gè)問題,有兩種解決方案。
方案一:將這多個(gè)參數(shù)封裝為一個(gè)map
將這多個(gè)參數(shù)封裝為一個(gè)Map<String,Object>,根據(jù)Map進(jìn)行查詢。
1.dao接口
List<Student> selectStudentByMap(Map<String, Object> map);
2.測(cè)試類
@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>
方案二:多個(gè)參數(shù)逐個(gè)接收
對(duì)于mapper中的SQL語句,可以通過參數(shù)索引#{index}的方式逐個(gè)接收每個(gè)參數(shù)。
1.dao接口
List<Student> selectStudentByConditions(String name,int age);
2.測(cè)試類
@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>
動(dòng)態(tài)SQL
動(dòng)態(tài)sql,主要用于解決查詢條件不確定的情況:在程序運(yùn)行期間,根據(jù)用戶提交的查詢條件進(jìn)行查詢。提交的查詢條件不同,執(zhí)行的sql語句不同。若將每種可能的情況均逐一列出,對(duì)所有條件進(jìn)行排列組合,將會(huì)出現(xiàn)大量的sql語句。此時(shí),可使用動(dòng)態(tài)sql來解決這樣的問題
動(dòng)態(tài)sql,即通過MyBatis提供的各種標(biāo)簽對(duì)條件作出判斷以實(shí)現(xiàn)拼接sql語句
這里的條件判斷使用的表達(dá)式為OGNL表達(dá)式。常用的動(dòng)態(tài)SQL標(biāo)簽有<if>、<where>、<choose/>、<foreach>等。
1.實(shí)體類:
public class Student {
private Integer id;
private String name;
private int age;
private double score;
//無參構(gòu)造器與帶參構(gòu)造器
//getter 和 setter
//toString()
}
2.測(cè)試類
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();
}
}
}
注意事項(xiàng):
在mapper的動(dòng)態(tài)sql中若出現(xiàn)大于號(hào)(>)、小于號(hào)(<)、大于等于號(hào)(>=)、小于等于號(hào)(<=)等符號(hào),最好將其轉(zhuǎn)換為實(shí)體符號(hào)。否則,xml可能會(huì)出現(xiàn)解析出錯(cuò)問題。
1.<if/>標(biāo)簽
對(duì)于該標(biāo)簽的執(zhí)行,當(dāng)test的值為true時(shí),會(huì)將其包含的sql片段拼接到其后所在的sql語句中。
例:查詢出滿足用戶提交查詢條件的所有學(xué)生。用戶提交的查詢條件可以包含一個(gè)姓名的模糊查詢,同時(shí)還可以包含一個(gè)年齡的下限。當(dāng)然,用戶在提交表單時(shí)可能兩個(gè)條件均作出了設(shè)定,也可能兩個(gè)條件均不做設(shè)定,也可以只做其中一項(xiàng)設(shè)定。
查詢條件不確定,查詢條件依賴于用戶提交的內(nèi)容。此時(shí),就可使用動(dòng)態(tài)sql語句,根據(jù)用戶提交內(nèi)容對(duì)將要執(zhí)行的sql進(jìn)行拼接。
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>
測(cè)試類:
@Test
public void test01(){
Student student = new Student();
List<Student> students = dao.selectStudentsIf(student);
System.out.println(students);
}
2.<where/>標(biāo)簽
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>
測(cè)試:
@Test
public void test02(){
Student student = new Student();
List<Student> students = dao.selectStudentsWhere(student);
System.out.println(students);
}
3.<choose/>標(biāo)簽
該標(biāo)簽只可以包含<when/><otherwise/>,可以包含多個(gè)<when/>與一個(gè)<otherwise/>。他們聯(lián)合使用,完成java中的開關(guān)語句switch..case功能。
需求:若姓名不空,則按姓名查詢;若姓名為空,則按年齡查詢;若沒有查詢條件,則沒有查詢結(jié)果。
dao接口:
public interface IStudentDao {
List<Student> selectStudentsIf(Student student);
List<Student> selectStudentsWhere(Student student);
List<Student> selectStudentsChoose(Student student);
}
映射文件:
對(duì)于<choose/>標(biāo)簽,其會(huì)從第一個(gè)<when/>開始逐個(gè)向后進(jìn)行條件判斷。若出現(xiàn)<when/>中的test屬性值為true的情況,則直接結(jié)束<choose/>標(biāo)簽,不再向后進(jìn)行判斷查找。若所有<when/>的test判斷結(jié)果均為false,則最后會(huì)執(zhí)行<otherwise/>標(biāo)簽。
<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>
測(cè)試類:
@Test
public void test03(){
Student student = new Student();
List<Student> students = dao.selectStudentsChoose(student);
System.out.println(students);
}
4.<foreach/>標(biāo)簽--遍歷數(shù)組
<foreach/>標(biāo)簽用于實(shí)現(xiàn)對(duì)于數(shù)組于集合的遍歷。對(duì)其使用,需要注意:
- collection表示要遍歷的集合類型,這里是數(shù)組,即array
- open、close、separator為對(duì)遍歷內(nèi)容的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);
}
映射文件
動(dòng)態(tài)sql的判斷中使用的都是OGNL表達(dá)式。OGNL表達(dá)式中的數(shù)組使用array表示,數(shù)組長(zhǎng)度使用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>
測(cè)試類:
@Test
public void test04(){
Object[] studentIds = new Object[]{1,3};
List<Student> students = dao.selectStudentsForeachArray(studentIds);
System.out.println(students);
}
5.<foreach/>標(biāo)簽--遍歷泛型為基本類型的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表達(dá)式中的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>
測(cè)試類:
@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/>標(biāo)簽--遍歷泛型為自定義類型的List
dao接口:
List<Student> selectStudentsForeachList2(List<Student> students);
映射文件:
注意,這里的當(dāng)前遍歷對(duì)象類型是List中的泛型,即是Student對(duì)象。
<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>
測(cè)試類:
@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/>標(biāo)簽執(zhí)行insert操作:
在執(zhí)行插入時(shí)要注意不能用外部的括號(hào):
/**
* 保存品牌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>
真正執(zhí)行時(shí)的sql語句:insert into tb_category_brand(category_id,brand_id) values(1000,1000),(1000,2000);
是分別每一組數(shù)據(jù)都有括號(hào),因?yàn)椴荒苁褂胦pen和close標(biāo)簽來追加括號(hào)在最外面,否則會(huì)報(bào)以下錯(cuò)誤:

7.<sql/>標(biāo)簽
<sql/>標(biāo)簽用于定義sql片段,以便其它sql標(biāo)簽復(fù)用。而其他標(biāo)簽使用該SQL片段,需要使用<include/>子標(biāo)簽。該<sql/>標(biāo)簽可以定義SQL語句中的任何部分,所以<include/>子標(biāo)簽可以放在動(dòng)態(tài)sql的任何位置
dao接口:
List<Student> selectStudentsBySQLFragment(List<Student> students);
映射文件:
測(cè)試類:
@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);
}