在上一章,我們看到了我們是怎樣在映射器Mapper XML配置文件中配置映射語句的。MyBatis也支持使用注解來配置映射語句。當我們使用基於注解的映射器接口時,我們不再需要在XML配置文件中配置了。如果你願意,你也可以同時使用基於XML和基於注解的映射語句。
本章將涵蓋以下話題:
- l 在映射器Mapper接口上使用注解
- l 映射語句
@Insert,@Update,@Delete,@SeelctStatements
- l 結果映射
一對一映射
一對多映射
- l 動態SQL
@SelectProvider
@InsertProvider
@UpdateProvider
@DeleteProvider
4.1 在映射器Mapper接口上使用注解
MyBatis對於大部分的基於XML的映射器元素(包括<select>,<update>)提供了對應的基於注解的配置項。然而在某些情況下,基於注解配置 還不能支持基於XML的一些元素。
4.2 映射語句
MyBatis提供了多種注解來支持不同類型的語句(statement)如SELECT,INSERT,UPDATE,DELETE。讓我們看一下具體怎樣配置映射語句。
4.2.1 @Insert
我們可以使用@Insert注解來定義一個INSERT映射語句:
使用了@Insert注解的insertMethod()方法將返回insert語句執行后影響的行數。
- package com.mybatis3.mappers;
- public interface StudentMapper
- {
- @Insert("INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,ADDR_ID, PHONE)
- VALUES(#{studId},#{name},#{email},#{address.addrId},#{phone})")
- int insertStudent(Student student);
- }
[自動生成主鍵]
在上一章中我們討論過主鍵列值可以自動生成。我們可以使用@Options注解的userGeneratedKeys 和keyProperty屬性讓數據庫產生auto_increment(自增長)列的值,然后將生成的值設置到輸入參數對象的屬性中。
- @Insert("INSERT INTO STUDENTS(NAME,EMAIL,ADDR_ID, PHONE)
- VALUES(#{name},#{email},#{address.addrId},#{phone})")
- @Options(useGeneratedKeys = true, keyProperty = "studId")
- int insertStudent(Student student);
這里STUD_ID列值將會通過MySQL數據庫自動生成。並且生成的值將會被設置到student對象的studId屬性中。
- StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
- mapper.insertStudent(student);
- int studentId = student.getStudId();
有一些數據庫如Oracle,並不支持AUTO_INCREMENT列屬性,它使用序列(SEQUENCE)來產生主鍵的值。
我們可以使用@SelectKey注解來為任意SQL語句來指定主鍵值,作為主鍵列的值。
假設我們有一個名為STUD_ID_SEQ的序列來生成STUD_ID主鍵值。
- @Insert("INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,ADDR_ID, PHONE)
- VALUES(#{studId},#{name},#{email},#{address.addrId},#{phone})")
- @SelectKey(statement="SELECT STUD_ID_SEQ.NEXTVAL FROM DUAL",
- keyProperty="studId", resultType=int.class, before=true)
- int insertStudent(Student student);
這里我們使用了@SelectKey來生成主鍵值,並且存儲到了student對象的studId屬性上。由於我們設置了before=true,該語句將會在執行INSERT語句之前執行。
如果你使用序列作為觸發器來設置主鍵值,我們可以在INSERT語句執行后,從sequence_name.currval獲取數據庫產生的主鍵值。
- @Insert("INSERT INTO STUDENTS(NAME,EMAIL,ADDR_ID, PHONE)
- VALUES(#{name},#{email},#{address.addrId},#{phone})")
- @SelectKey(statement="SELECT STUD_ID_SEQ.CURRVAL FROM DUAL",
- keyProperty="studId", resultType=int.class, before=false)
- int insertStudent(Student student);
4.2.2 @Update
我們可以使用@Update注解來定義一個UPDATE映射語句,如下所示:
- @Update("UPDATE STUDENTS SET NAME=#{name}, EMAIL=#{email},
- PHONE=#{phone} WHERE STUD_ID=#{studId}")
- int updateStudent(Student student);
使用了@Update的updateStudent()方法將會返回執行了update語句后影響的行數。
- StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
- int noOfRowsUpdated = mapper.updateStudent(student);
4.2.3 @Delete
我們可以使用@Delete 注解來定義一個DELETE映射語句,如下所示:
使用了@Delete的deleteStudent()方法將會返回執行了update語句后影響的行數。
- @Delete("DELETE FROM STUDENTS WHERE STUD_ID=#{studId}")
- int deleteStudent(int studId);
4.2.4 @Select
我們可以使用@ Select注解來定義一個SELECT映射語句。
讓我們看一下怎樣使用注解配置一個簡單的select查詢。
- package com.mybatis3.mappers;
- public interface StudentMapper
- {
- @Select("SELECT STUD_ID AS STUDID, NAME, EMAIL, PHONE FROM
- STUDENTS WHERE STUD_ID=#{studId}")
- Student findStudentById(Integer studId);
- }
為了將列名和Studentbean屬性名匹配,我們為stud_id起了一個studId的別名。如果返回了多行結果,將拋出 TooManyResultsException異常。
4.3 結果映射
我們可以將查詢結果通過別名或者是@Results注解與JavaBean屬性映射起來。
現在讓我們看看怎樣使用@Results注解將指定列於指定JavaBean屬性映射器來,執行SELECT查詢的:
- package com.mybatis3.mappers;
- public interface StudentMapper
- {
- @Select("SELECT * FROM STUDENTS")
- @Results(
- {
- @Result(id = true, column = "stud_id", property = "studId"),
- @Result(column = "name", property = "name"),
- @Result(column = "email", property = "email"),
- @Result(column = "addr_id", property = "address.addrId")
- })
- List<Student> findAllStudents();
- }
![]()
例如,看下面的findStudentById()和findAllStudents()方法:
- @Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}")
- @Results(
- {
- @Result(id = true, column = "stud_id", property = "studId"),
- @Result(column = "name", property = "name"),
- @Result(column = "email", property = "email"),
- @Result(column = "addr_id", property = "address.addrId")
- })
- Student findStudentById(int studId);
- @Select("SELECT * FROM STUDENTS")
- @Results(
- {
- @Result(id = true, column = "stud_id", property = "studId"),
- @Result(column = "name", property = "name"),
- @Result(column = "email", property = "email"),
- @Result(column = "addr_id", property = "address.addrId")
- })
- List<Student> findAllStudents();
這里兩個語句的@Results配置完全相同,但是我必須得重復它。這里有一個解決方法。我們可以創建一個映射器Mapper配置文件, 然后配置<resultMap>元素,然后使用@ResultMap注解引用此<resultMap>。
在StudentMapper.xml中定義一個ID為StudentResult的<resultMap>。
- <mapper namespace="com.mybatis3.mappers.StudentMapper">
- <resultMap type="Student" id="StudentResult">
- <id property="studId" column="stud_id" />
- <result property="name" column="name" />
- <result property="email" column="email" />
- <result property="phone" column="phone" />
- </resultMap>
- </mapper>
在StudentMapper.java中,使用@ResultMap引用名為StudentResult的resultMap。
- public interface StudentMapper
- {
- @Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}")
- @ResultMap("com.mybatis3.mappers.StudentMapper.StudentResult")
- Student findStudentById(int studId);
- @Select("SELECT * FROM STUDENTS")
- @ResultMap("com.mybatis3.mappers.StudentMapper.StudentResult")
- List<Student> findAllStudents();
- }
4.3.1 一對一映射
MyBatis提供了@One注解來使用嵌套select語句(Nested-Select)加載一對一關聯查詢數據。讓我們看看怎樣使用@One注解獲取學生及其地址信息。
- public interface StudentMapper
- {
- @Select("SELECT ADDR_ID AS ADDRID, STREET, CITY, STATE, ZIP, COUNTRY
- FROM ADDRESSES WHERE ADDR_ID=#{id}")
- Address findAddressById(int id);
- @Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId} ")
- @Results(
- {
- @Result(id = true, column = "stud_id", property = "studId"),
- @Result(column = "name", property = "name"),
- @Result(column = "email", property = "email"),
- @Result(property = "address", column = "addr_id",
- one = @One(select = "com.mybatis3.mappers.StudentMapper.
- findAddressById"))
- })
- Student selectStudentWithAddress(int studId);
- }
這里我們使用了@One注解的select屬性來指定一個使用了完全限定名的方法上,該方法會返回一個Address對象。使用column=”addr_id”,則STUEDNTS表中列addr_id的值將會作為輸入參數傳遞給findAddressById()方法。如果@OneSELECT查詢返回了多行結果,則會拋出TooManyResultsException異常。
- int studId = 1;
- StudentMapper studentMapper =
- sqlSession.getMapper(StudentMapper.class);
- Student student = studentMapper.selectStudentWithAddress(studId);
- System.out.println("Student :"+student);
- System.out.println("Address :"+student.getAddress());
在第三章,使用XML配置SQL映射器中我們討論過,我們可以通過基於XML的映射器配置,使用嵌套結果ResultMap來加載一對一關聯的查詢。而MyBatis3.2.2版本,並沒有對應的注解支持。但是我們可以在映射器Mapper配置文件中配置<resultMap>並且使用@ResultMap注解來引用它。
在StudentMapper.xml中配置<resultMap>,如下所示:
- <mapper namespace="com.mybatis3.mappers.StudentMapper">
- <resultMap type="Address" id="AddressResult">
- <id property="addrId" column="addr_id" />
- <result property="street" column="street" />
- <result property="city" column="city" />
- <result property="state" column="state" />
- <result property="zip" column="zip" />
- <result property="country" column="country" />
- </resultMap>
- <resultMap type="Student" id="StudentWithAddressResult">
- <id property="studId" column="stud_id" />
- <result property="name" column="name" />
- <result property="email" column="email" />
- <association property="address" resultMap="AddressResult" />
- </resultMap>
- </mapper>
- public interface StudentMapper
- {
- @Select("select stud_id, name, email, a.addr_id, street, city,
- state, zip, country" + " FROM students s left outer join addresses a
- on s.addr_id=a.addr_id" + " where stud_id=#{studId} ")
- @ResultMap("com.mybatis3.mappers.StudentMapper.
- StudentWithAddressResult")
- Student selectStudentWithAddress(int id);
- }
4.3.2 一對多映射
MyBatis提供了@Many注解,用來使用嵌套Select語句加載一對多關聯查詢。
現在讓我們看一下如何使用@Many注解獲取一個講師及其教授課程列表信息:
- public interface TutorMapper
- {
- @Select("select addr_id as addrId, street, city, state, zip,
- country from addresses where addr_id=#{id}")
- Address findAddressById(int id);
- @Select("select * from courses where tutor_id=#{tutorId}")
- @Results(
- {
- @Result(id = true, column = "course_id", property = "courseId"),
- @Result(column = "name", property = "name"),
- @Result(column = "description", property = "description"),
- @Result(column = "start_date" property = "startDate"),
- @Result(column = "end_date" property = "endDate")
- })
- List<Course> findCoursesByTutorId(int tutorId);
- @Select("SELECT tutor_id, name as tutor_name, email, addr_id
- FROM tutors where tutor_id=#{tutorId}")
- @Results(
- {
- @Result(id = true, column = "tutor_id", property = "tutorId"),
- @Result(column = "tutor_name", property = "name"),
- @Result(column = "email", property = "email"),
- @Result(property = "address", column = "addr_id",
- one = @One(select = " com.mybatis3.
- mappers.TutorMapper.findAddressById")),
- @Result(property = "courses", column = "tutor_id",
- many = @Many(select = "com.mybatis3.mappers.TutorMapper.
- findCoursesByTutorId"))
- })
- Tutor findTutorById(int tutorId);
- }
這里我們使用了@Many注解的select屬性來指向一個完全限定名稱的方法,該方法將返回一個List<Course>對象。使用column=”tutor_id”,TUTORS表中的tutor_id列值將會作為輸入參數傳遞給findCoursesByTutorId()方法。
在第三章,使用XML配置SQL映射器中我們討論過,我們可以通過基於XML的映射器配置,使用嵌套結果ResultMap來加載一對多關聯的查詢。而MyBatis3.2.2版本,並沒有對應的注解支持。但是我們可以在映射器Mapper配置文件中配置<resultMap>並且使用@ResultMap注解來引用它。
在TutorMapper.xml中配置<resultMap>,如下所示:
- <mapper namespace="com.mybatis3.mappers.TutorMapper">
- <resultMap type="Address" id="AddressResult">
- <id property="addrId" column="addr_id" />
- <result property="street" column="street" />
- <result property="city" column="city" />
- <result property="state" column="state" />
- <result property="zip" column="zip" />
- <result property="country" column="country" />
- </resultMap>
- <resultMap type="Course" id="CourseResult">
- <id column="course_id" property="courseId" />
- <result column="name" property="name" />
- <result column="description" property="description" />
- <result column="start_date" property="startDate" />
- <result column="end_date" property="endDate" />
- </resultMap>
- <resultMap type="Tutor" id="TutorResult">
- <id column="tutor_id" property="tutorId" />
- <result column="tutor_name" property="name" />
- <result column="email" property="email" />
- <association property="address" resultMap="AddressResult" />
- <collection property="courses" resultMap="CourseResult" />
- </resultMap>
- </mapper>
- public interface TutorMapper
- {
- @Select("SELECT T.TUTOR_ID, T.NAME AS TUTOR_NAME, EMAIL,
- A.ADDR_ID, STREET, CITY, STATE, ZIP, COUNTRY, COURSE_ID, C.NAME,
- DESCRIPTION, START_DATE, END_DATE FROM TUTORS T LEFT OUTER
- JOIN ADDRESSES A ON T.ADDR_ID=A.ADDR_ID LEFT OUTER JOIN COURSES
- C ON T.TUTOR_ID=C.TUTOR_ID WHERE T.TUTOR_ID=#{tutorId}")
- @ResultMap("com.mybatis3.mappers.TutorMapper.TutorResult")
- Tutor selectTutorById(int tutorId);
- }
4.4 動態SQL
有時候我們需要根據輸入條件動態地構建SQL語句。MyBatis提供了各種注解如@InsertProvider,@UpdateProvider,@DeleteProvider和@SelectProvider,來幫助構建動態SQL語句,然后讓MyBatis執行這些SQL語句。
4.4.1 @SelectProvider
現在讓我們來看一個使用@SelectProvider注解來創建一個簡單的SELECT映射語句的例子。
創建一個TutorDynaSqlProvider.java類,以及findTutorByIdSql()方法,如下所示:在TutorMapper.java接口中創建一個映射語句,如下:
- package com.mybatis3.sqlproviders;
- import org.apache.ibatis.jdbc.SQL;
- public class TutorDynaSqlProvider
- {
- public String findTutorByIdSql(int tutorId)
- {
- return "SELECT TUTOR_ID AS tutorId, NAME, EMAIL FROM TUTORS
- WHERE TUTOR_ID=" + tutorId;
- }
- }
- @SelectProvider(type=TutorDynaSqlProvider.class, method="findTutorByIdSql")
- Tutor findTutorById(int tutorId);
這里我們使用了@SelectProvider來指定了一個類,及其內部的方法,用來提供需要執行的SQL語句。
但是使用字符串拼接的方法唉構建SQL語句是非常困難的,並且容易出錯。所以MyBaits提供了一個SQL工具類不使用字符串拼接的方式,簡化構造動態SQL語句。
現在,讓我們看看如何使用org.apache.ibatis.jdbc.SQL工具類來准備相同的SQL語句。
- package com.mybatis3.sqlproviders;
- import org.apache.ibatis.jdbc.SQL;
- public class TutorDynaSqlProvider
- {
- public String findTutorByIdSql(final int tutorId)
- {
- return new SQL()
- {
- {
- SELECT("tutor_id as tutorId, name, email");
- FROM("tutors");
- WHERE("tutor_id=" + tutorId);
- }
- } .toString();
- }
- }
SQL工具類會處理以合適的空格前綴和后綴來構造SQL語句。
動態SQL provider方法可以接收以下其中一種參數:
- ž 無參數
- ž 和映射器Mapper接口的方法同類型的參數
- ž java.util.Map
如果SQL語句的准備不取決於輸入參數,你可以使用不帶參數的SQL Provider方法。
例如:
- public String findTutorByIdSql()
- {
- return new SQL()
- {
- {
- SELECT("tutor_id as tutorId, name, email");
- FROM("tutors");
- WHERE("tutor_id = #{tutorId}");
- }
- } .toString();
- }
這里我們沒有使用輸入參數構造SQL語句,所以它可以是一個無參方法。
如果映射器Mapper接口方法只有一個參數,那么可以定義SQLProvider方法,它接受一個與Mapper接口方法相同類型的參數。
例如映射器Mapper接口有如下定義:
- <span style="font-family:Microsoft YaHei;font-size:12px;">Tutor findTutorById(int tutorId);</span>
這里findTutorById(int)方法只有一個int類型的參數。我們可以定義findTutorByIdSql(int)方法作為SQL provider方法。
如果映射器Mapper接口有多個輸入參數,我們可以使用參數類型為java.util.Map的方法作為SQLprovider方法。然后映射器Mapper接口方法所有的輸入參數將會被放到map中,以param1,param2等等作為key,將輸入參數按序作為value。你也可以使用0,1,2等作為key值來取的輸入參數。
- public String findTutorByIdSql(final int tutorId)
- {
- return new SQL()
- {
- {
- SELECT("tutor_id as tutorId, name, email");
- FROM("tutors");
- WHERE("tutor_id=" + tutorId);
- }
- } .toString();
- }
- @SelectProvider(type = TutorDynaSqlProvider.class,
- method = "findTutorByNameAndEmailSql")
- Tutor findTutorByNameAndEmail(String name, String email);
- public String findTutorByNameAndEmailSql(Map<String, Object> map)
- {
- String name = (String) map.get("param1");
- String email = (String) map.get("param2");
- //you can also get those values using 0,1 keys
- //String name = (String) map.get("0");
- //String email = (String) map.get("1");
- return new SQL()
- {
- {
- SELECT("tutor_id as tutorId, name, email");
- FROM("tutors");
- WHERE("name=#{name} AND email=#{email}");
- }
- } .toString();
- }
SQL工具類也提供了其他的方法來表示JOINS,ORDER_BY,GROUP_BY等等。
讓我們看一個使用LEFT_OUTER_JOIN的例子:由於沒有支持使用內嵌結果ResultMap的一對多關聯映射的注解支持,我們可以使用基於XML的<resultMap>配置,然后與@ResultMap映射。
- public class TutorDynaSqlProvider
- {
- public String selectTutorById()
- {
- return new SQL()
- {
- {
- SELECT("t.tutor_id, t.name as tutor_name, email");
- SELECT("a.addr_id, street, city, state, zip, country");
- SELECT("course_id, c.name as course_name, description,
- start_date, end_date");
- FROM("TUTORS t");
- LEFT_OUTER_JOIN("addresses a on t.addr_id=a.addr_id");
- LEFT_OUTER_JOIN("courses c on t.tutor_id=c.tutor_id");
- WHERE("t.TUTOR_ID = #{id}");
- }
- } .toString();
- }
- }
- public interface TutorMapper
- {
- @SelectProvider(type = TutorDynaSqlProvider.class,
- method = "selectTutorById")
- @ResultMap("com.mybatis3.mappers.TutorMapper.TutorResult")
- Tutor selectTutorById(int tutorId);
- }
使用了動態的SQL provider,我們可以取得講師及其地址和課程明細。
- <mapper namespace="com.mybatis3.mappers.TutorMapper">
- <resultMap type="Address" id="AddressResult">
- <id property="id" column="addr_id" />
- <result property="street" column="street" />
- <result property="city" column="city" />
- <result property="state" column="state" />
- <result property="zip" column="zip" />
- <result property="country" column="country" />
- </resultMap>
- <resultMap type="Course" id="CourseResult">
- <id column="course_id" property="id" />
- <result column="course_name" property="name" />
- <result column="description" property="description" />
- <result column="start_date" property="startDate" />
- <result column="end_date" property="endDate" />
- </resultMap>
- <resultMap type="Tutor" id="TutorResult">
- <id column="tutor_id" property="id" />
- <result column="tutor_name" property="name" />
- <result column="email" property="email" />
- <association property="address" resultMap="AddressResult" />
- <collection property="courses" resultMap="CourseResult"></collection>
- </resultMap>
- </mapper>
4.4.2 @InsertProvider
我們可以使用@InsertProvider注解創建動態的INSERT語句,如下所示:
- public class TutorDynaSqlProvider
- {
- public String insertTutor(final Tutor tutor)
- {
- return new SQL()
- {
- {
- INSERT_INTO("TUTORS");
- if (tutor.getName() != null)
- {
- VALUES("NAME", "#{name}");
- }
- if (tutor.getEmail() != null)
- {
- VALUES("EMAIL", "#{email}");
- }
- }
- } .toString();
- }
- }
- public interface TutorMapper
- {
- @InsertProvider(type = TutorDynaSqlProvider.class,
- method = "insertTutor")
- @Options(useGeneratedKeys = true, keyProperty = "tutorId")
- int insertTutor(Tutor tutor);
- }
4.4.3 @UpdateProvider
我們可以通過@UpdateProvider注解創建UPDATE語句,如下所示:
- public class TutorDynaSqlProvider
- {
- public String updateTutor(final Tutor tutor)
- {
- return new SQL()
- {
- {
- UPDATE("TUTORS");
- if (tutor.getName() != null)
- {
- SET("NAME = #{name}");
- }
- if (tutor.getEmail() != null)
- {
- SET("EMAIL = #{email}");
- }
- WHERE("TUTOR_ID = #{tutorId}");
- }
- } .toString();
- }
- }
- public interface TutorMapper
- {
- @UpdateProvider(type = TutorDynaSqlProvider.class,
- method = "updateTutor")
- int updateTutor(Tutor tutor);
- }
4.4.4 @DeleteProvider
我們可以使用@DeleteProvider注解創建動態地DELETE語句,如下所示:
- public class TutorDynaSqlProvider
- {
- public String deleteTutor(int tutorId)
- {
- return new SQL()
- {
- {
- DELETE_FROM("TUTORS");
- WHERE("TUTOR_ID = #{tutorId}");
- }
- } .toString();
- }
- }
- public interface TutorMapper
- {
- @DeleteProvider(type = TutorDynaSqlProvider.class,
- method = "deleteTutor")
- int deleteTutor(int tutorId);
- }
4.5 總結
在本章中,我們學習了怎樣使用注解書寫SQL映射語句。討論了如何配置簡單語句,一對一關系語句和一對多關系語句。我們還探討了怎樣使用SqlProvider注解來構建動態SQL語句。在下一章,我們將討論如何將MyBatis與Spring框架集成。