一個半自動化的orm框架(Object Relation Mapping)。java
在面向對象編程中,咱們操做的都是對象,Mybatis框架是一個數據訪問層的框架,幫咱們完成對象在數據庫中的
存、取工做。正則表達式
爲何稱爲半自動化?spring
關係型數據庫的操做是經過SQL語句來完成的,Mybatis在幫咱們作對象的存取時,須要咱們提供對應的SQL語句,它不自動幫咱們生成SQL語句,而只幫咱們完成:sql
1)對象屬性到SQL語句參數的自動填充;
2)SQL語句執行結果集到對象的自動提取;
因此稱爲半自動的。而咱們瞭解的另外一個ORM框架Hibernate則是全自動的。數據庫
半自動化的不足:咱們得辛苦一點編寫SQL語句。
半自動化的優勢:咱們能夠徹底把控執行的SQL語句,能夠隨時靈活調整、優化。編程
1)mybatis學習、使用簡單
2)半自動化的優勢數組
一線互聯網公司出於性能、調優、使用簡單、徹底可控的須要,在數據庫訪問層都是採用Mybatis。安全
都是爲了提升生產效率,少寫代碼,少寫重複代碼!
不用orm框架,能用什麼來完成數據的存取?
jdbc
看看jdbc編程的代碼示例session
package com.study.leesmall.sample.mybatis.jdbc; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import com.study.leesmall.sample.mybatis.model.User; @Component public class UserDao { @Autowired private DataSource dataSource; public void addUser(User user) throws SQLException { try ( // 一、獲取鏈接 Connection conn = DataSourceUtils.getConnection(dataSource); // 二、建立預編譯語句對象 PreparedStatement pst = conn.prepareStatement( "insert into t_user(id,name,sex,age,address,phone,wechat,email,account,password) " + " values(?,?,?,?,?,?,?,?,?,?)");) { // 三、設置參數值 int i = 1; pst.setString(i++, user.getId()); pst.setString(i++, user.getName()); pst.setString(i++, user.getSex()); pst.setInt(i++, user.getAge()); pst.setString(i++, user.getAddress()); pst.setString(i++, user.getPhone()); pst.setString(i++, user.getWechat()); pst.setString(i++, user.getEmail()); pst.setString(i++, user.getAccount()); pst.setString(i++, user.getPassword()); // 四、執行語句 int changeRows = pst.executeUpdate(); } } public List<User> queryUsers(String likeName, int minAge, int maxAge, String sex) throws SQLException { // 一、根據查詢條件動態拼接SQL語句 StringBuffer sql = new StringBuffer( "select id,name,sex,age,address,phone,wechat,email,account,password from t_user where 1 = 1 "); if (!StringUtils.isEmpty(likeName)) { sql.append(" and name like ? "); } if (minAge >= 0) { sql.append(" and age >= ? "); } if (maxAge >= 0) { sql.append(" and age <= ? "); } if (!StringUtils.isEmpty(sex)) { sql.append(" and sex = ? "); } try (Connection conn = DataSourceUtils.getConnection(dataSource); PreparedStatement pst = conn.prepareStatement(sql.toString());) { // 2 設置查詢語句參數值 int i = 1; if (!StringUtils.isEmpty(likeName)) { pst.setString(i++, "%" + likeName + "%"); } if (minAge >= 0) { pst.setInt(i++, minAge); } if (maxAge >= 0) { pst.setInt(i++, maxAge); } if (!StringUtils.isEmpty(sex)) { pst.setString(i++, sex); } // 3 執行查詢 ResultSet rs = pst.executeQuery(); // 四、提取結果集 List<User> list = new ArrayList<>(); User u; while (rs.next()) { u = new User(); list.add(u); u.setId(rs.getString("id")); u.setName(rs.getString("name")); u.setSex(rs.getString("sex")); u.setAge(rs.getInt("age")); u.setPhone(rs.getString("phone")); u.setEmail(rs.getString("email")); u.setWechat(rs.getString("wechat")); u.setAccount(rs.getString("account")); u.setPassword(rs.getString("password")); } rs.close(); return list; } } }
用JdbcTemplate的代碼示例:mybatis
package com.study.leesmall.sample.mybatis.jdbc; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.util.StringUtils; import com.study.leesmall.sample.mybatis.model.User; //@Component public class UserDaoUseJdbcTemplate { @Autowired private JdbcTemplate jdbcTemplate; public void addUser(User user) throws SQLException { String sql = "insert into t_user(id,name,sex,age,address,phone,wechat,email,account,password) " + " values(?,?,?,?,?,?,?,?,?,?)"; jdbcTemplate.update(sql, user.getId(), user.getName(), user.getSex(), user.getAge(), user.getAddress(), user.getPhone(), user.getWechat(), user.getEmail(), user.getAccount(), user.getPassword()); } public List<User> queryUsers(String likeName, int minAge, int maxAge, String sex) throws SQLException { // 一、根據查詢條件動態拼接SQL語句 StringBuffer sql = new StringBuffer( "select id,name,sex,age,address,phone,wechat,email,account,password from t_user where 1 = 1 "); List<Object> argList = new ArrayList<>(); if (!StringUtils.isEmpty(likeName)) { sql.append(" and name like ? "); argList.add("%" + likeName + "%"); } if (minAge >= 0) { sql.append(" and age >= ? "); argList.add(minAge); } if (maxAge >= 0) { sql.append(" and age <= ? "); argList.add(maxAge); } if (!StringUtils.isEmpty(sex)) { sql.append(" and sex = ? "); argList.add(sex); } return jdbcTemplate.query(sql.toString(), argList.toArray(), new RowMapper<User>() { public User mapRow(ResultSet rs, int rowNum) throws SQLException { User u = new User(); u.setId(rs.getString("id")); u.setName(rs.getString("name")); u.setSex(rs.getString("sex")); u.setAge(rs.getInt("age")); u.setPhone(rs.getString("phone")); u.setEmail(rs.getString("email")); u.setWechat(rs.getString("wechat")); u.setAccount(rs.getString("account")); u.setPassword(rs.getString("password")); return u; } }); } }
參數設置代碼、結果集處理代碼、JDBC過程代碼都會大量重複,毫無技術含量!
那就寫個框架作了它!顯示咱們的牛B!
1)用戶只需定義持久層接口(dao接口)、接口方法對應的SQL語句。
2)用戶需指明接口方法的參數與SQL語句參數的對應關係。
3)用戶需指明SQL查詢結果集與對象屬性的映射關係。
4)框架完成接口對象的生成,JDBC執行過程
1)用戶只需定義持久層接口(dao接口)、接口方法對應的SQL語句。
設計問題:
1)咱們該提供什麼樣的方式來讓用戶定義SQL語句?
2)SQL語句怎麼與接口方法對應?
3)這些SQL語句、對應關係咱們框架須要獲取到,誰來獲取?又該如何表示存儲
XML方式:獨立於代碼,修改很方便(不需改代碼)
註解方式:直接加在方法上,零xml配置
問題:SQL語句可作增、刪、改、查操做,咱們是否要對SQL作個區分?
答:要,由於jdbc中對應有不一樣的方法 executeQuery executeUpdate
xml方式定義SQL語句定義方式 :設計增刪改查的元素:
<!ELEMENT insert(#PCDATA) > <!ELEMENT update(#PCDATA) > <!ELEMENT delete(#PCDATA) > <!ELEMENT select (#PCDATA) >
<insert>insert into t_user(id,name,sex,age) values(?,?,?,?)</insert>
註解方式定義SQL語句定義方式 :設計增刪改查的註解:@Insert @Update @Delete @Select ,註解項定義SQL
@Documented @Retention(RUNTIME) @Target({ METHOD }) public @interface Insert { String value(); }
@Insert("insert into t_user(id,name,sex,age) values(?,?,?,?)") public void addUser(User user);
xml方式時,如何來映射SQL語句對應的接口方法?
爲元素定義一個id,id的值爲對應的類名.方法名,如何?
<insert id="com.study.leesmall.sample.UserDao.addUser"> insert into t_user(id,name,sex,age) values(?,?,?,?) </insert>
一個Dao接口中可能會定義不少個數據訪問方法,id這麼寫很長,能不能便捷一點?
這是在作SQL與接口方法的映射,咱們來加一個mapper元素,它可包含多個insert、update、delete、select元素,至關於分組,一個接口中定義的分到一組。
在mapper中定義一個屬性namespace,指定裏面元素的名稱空間,namespace的值對應接口類名,裏面元素的id對應方法名。
mybatis-mapper.dtd
<!ELEMENT mapper (insert* | update* | delete* | select*)+ >
<!ATTLIST mapper namespace CDATA #IMPLIED >
<!ELEMENT insert(#PCDATA) >
<!ELEMENT update(#PCDATA) >
<!ELEMENT delete(#PCDATA) >
<!ELEMENT select (#PCDATA) >
這個xml文件命名爲 userDaoMapper.xml,內容以下:
<mapper namespace="com.study.leesmall.sample.userDao"> <insert id="addUser"> insert into t_user(id,name,sex,age) values(?,?,?,?) </insert> </mapper>
xml方式:
解析xml來獲取
註解方式:
讀取註解信息
問題:
1) 怎麼表示?
得設計一個類來表示從xml、註解得到的SQL映射信息。
注意:
id爲惟一id:
xml方式:id=namespace.id屬性值
註解方式:id=完整類名.方法名
2) 怎麼存儲獲得的MappedStatement?
這些其實就是一個配置信息,咱們定義一個Configuration類:
注意:key 爲MappedStatement的id
3) 得有類來負責解析xml
XmlMapperBuilder負責解析xml文檔(parse方法的resource參數用來指定inputStream的來源),parse方法它調用XMLStatementBuilder來解析裏面的parse方法,解析完成之後,把得到的信息存儲到Configuration裏面
4)mapper中可讓用戶如何來指定文件位置?
文件能夠是在類目錄下,也但是在文件系統目錄下。如何區分?
規定:
類目錄下的方式經過 resource屬性指定;
文件系統文件經過 url屬性指定,值採用URL 本地文件格式指定:file:///
<configuration> <mappers> <mapper resource="com/leesmall/UserMapper.xml"/> <mapper url="file:///var/mappers/CourseMapper.xml"/> <mappers> </configuration>
定義 mybatis-config.dtd
<!ELEMENT configuration (mappers?)+ > <!ELEMENT mappers (mapper*)> <!ELEMENT mapper EMPTY> <!ATTLIST mapper resource CDATA #IMPLIED url CDATA #IMPLIED >
5) 增長了一個config xml文件,就的有類來解析它。
增長解析mybatis-config.xml配置文件的類
6) 註解的方式須要獲取SQL映射信息,也得有個類來作這件事
7) 誰來使用MapperAnnotationBuilder?
Configuration吧,在它裏面持有MapperAnnotationBuilder,增長添加Mapper接口類的方法。
8) 用戶如何來指定他們的Mapper接口類?
一、在mybatis-config.xml的mappers中經過mapper指定?
<configuration> <mappers> <mapper resource="com/leesmall/UserMapper.xml"/> <mapper url="file:///var/mappers/CourseMapper.xml"/> <mappers> </configuration>
如何來區分它是個Mapper接口呢?
給mapper加一個屬性class來專門指定Mapper類名
<configuration> <mappers> <mapper resource="com/leesmall/UserMapper.xml"/> <mapper url="file:///var/mappers/CourseMapper.xml"/> <mapper class="com.study.leesmall.dao.UserDao" /> <mappers> </configuration>
mybatis-config.dtd
<!ELEMENT configuration (mappers?)+ > <!ELEMENT mappers (mapper*)> <!ELEMENT mapper EMPTY> <!ATTLIST mapper resource CDATA #IMPLIED url CDATA #IMPLIED class CDATA #IMPLIED >
問題:
一、這樣一個一個類來指定,好繁瑣?能不能指定一個包名,包含包下全部接口、子孫包下的接口類?
二、包含包下全部的接口,好像不是很靈活,能不能讓用戶指定包下全部某類型的接口?
如是什麼類型的類,或帶有某註解的接口。
好的,這很容易,在mappers元素中增長一個package元素,pacakge元素定義三個屬性
mybatis-config.dtd
<!ELEMENT configuration (mappers?)+ > <!ELEMENT mappers (mapper*,package*)> <!ELEMENT mapper EMPTY> <!ATTLIST mapper resource CDATA #IMPLIED url CDATA #IMPLIED class CDATA #IMPLIED > <!ELEMENT package EMPTY> <!ATTLIST package name CDATA #IMPLIED type CDATA #IMPLIED annotation CDATA #IMPLIED >
<configuration> <mappers> <mapper resource="com/leesmall/UserMapper.xml"/> <mapper url="file:///var/mappers/CourseMapper.xml"/> <mapper class="com.study.leesmall.dao.UserDao" /> <package name="com.study.leesmall.mapper" /> <package name="com.study.leesmall.mapper" type="com.study.leesmall.MapperInterface"/> <package name="com.study.leesmall.mapper" annotation="com.study.leesmall.mybtis.annotation.Mapper"/> <package name="com.study.leesmall.mapper" type="com.study.leesmall.MapperInterface" annotation="com.study.leesmall.mybtis.annotation.Mapper"/> <mappers> </configuration>
爲了用戶使用方便,咱們給定義一個@Mapper註解,默認規則:指定包下加了@Mapper註解的接口,如何?
加了package元素,又得在Configuration中增長對應的方法了:
約定俗成的規則:指定包下掃到的@Mapper接口,例如UserDao,還能夠在包下定義 UserDao.xml,會被加載解析。
需求二、用戶需指明接口方法的參數與語句參數的對應關係。
看下面的Mapper示例
@Mapper public interface UserDao { @Insert("insert into t_user(id,name,sex,age) values(?,?,?,?)") void addUser(User user); }
User對象的屬性如何與 values(?)對應?
靠解析 t_user(id,name,sex,age) 可行嗎?
難度太大!
萬一User的name叫xname呢!
既然靠咱們來解析不行,那就請用戶指明吧。用戶如何來指明呢?
咱們來給定個規則: ? 用 #{屬性名} 代替,咱們來解析SQL語句中的 #{屬性名} 來決定參數對應。
@Insert("insert into t_user(id,name,sex,age) values(#{id},#{name},#{sex},#{age})") void addUser(User user);
萬一是這種狀況呢?
@Insert("insert into t_user(id,name,sex,age) values(#{id},#{name},#{sex},#{age})") void addUser(String id,String xname,String sex,int age);
徹底能夠要求用戶必須與參數名對應 :#{xname}。
爲了提升點自由度(及後面方便SQL複用),能夠定義一個註解讓用戶使用,該註解只可用在參數上
@Insert("insert into t_user(id,name,sex,age) values(#{id},#{name},#{sex},#{age})") void addUser(String id,@Param("name")String xname,String sex,int age);
萬一是這種狀況呢?
@Insert("insert into t_user(id,name,sex,age,org_id) values(#{id},#{name},#{sex},#{age},#{id})") void addUser(User user,Org org);
User和Org中都有id屬性,name屬性
好辦,若是方法參數是對象,則以 參數名.屬性 的方式指定SQL參數:
@Insert("insert into t_user(id,name,sex,age,org_id) values(#{user.id},#{user.name},#{user.sex},#{user.age},#{org.id})") void addUser(User user,Org org);
同樣也可使用@Param
若是方法參數是這種狀況呢?
@Insert("insert into t_user(id,name,sex,age) values(#{id},#{name},#{sex},#{age})") void addUser(Map map);
對應Map中的key
若是方法參數是這種狀況呢?
@Insert("insert into t_user(id,name,sex,age,org_id) values(#{user.id},#{user.name},#{user.sex},#{user.age},#{org.id})") void addUser(Map map,Org org);
@Insert("insert into t_user(id,name,sex,age,org_id) values(#{user.id},#{user.name},#{user.sex},#{user.age},#{org.id})") void addUser(@Param("user")Map map,Org org);
再來看下下面的場景:
@Select("select id,name,sex from t_user where sex = #{sex} order by #{orderColumn}")
List<User> query(String sex, String orderColumn);
order by #{orderColumn} order by ? 能夠嗎?
不能夠,也就是說 方法參數不全是用來作SQL語句的預編譯參數值的,有些是來構成SQL語句的一部分的。
那怎麼讓用戶指定呢?
同樣,定義個規則: ${屬性名} 表示這裏是字符串替換
@Select("select id,name,sex from t_user where sex = #{sex} order by ${orderColumn}")
List<User> query(String sex, String orderColumn);
問題:
1) SQL中參數映射解析要完成的是什麼工做?
解析出真正的SQL語句
得到方法參數與語句參數的對應關係 : 問號N---哪一個參
public void addUser(User user) throws SQLException { try ( // 一、獲取鏈接 Connection conn = DataSourceUtils.getConnection(dataSource); // 二、建立預編譯語句對象 PreparedStatement pst = conn.prepareStatement( "insert into t_user(id,name,sex,age,address,phone,wechat,email,account,password) " + " values(?,?,?,?,?,?,?,?,?,?)");) { // 三、設置參數值 int i = 1; pst.setString(i++, user.getId()); pst.setString(i++, user.getName()); pst.setString(i++, user.getSex()); pst.setInt(i++, user.getAge()); pst.setString(i++, user.getAddress()); pst.setString(i++, user.getPhone()); pst.setString(i++, user.getWechat()); pst.setString(i++, user.getEmail()); pst.setString(i++, user.getAccount()); pst.setString(i++, user.getPassword()); // 四、執行語句 int changeRows = pst.executeUpdate(); } }
怎麼解析?
@Insert("insert into t_user(id,name,sex,age,org_id) values(#{user.id},#{user.name},#{user.sex},#{user.age},#{org.id})") void addUser(@Param("user")Map map,Org org);
方式有:
正則表達式
antlr
怎麼表示?
問號的index、值來源
2) 這個解析的工做在什麼時候作好?誰來作好?
設計怎麼來執行一個Mapper接口了
SqlSession
SqlSessionFactory
1)用戶只需定義持久層接口(dao接口)、接口方法對應的SQL語句。
2)用戶需指明接口方法的參數與語句參數的對應關係。
3)用戶需指明查詢結果集與對象屬性的映射關係。
4)框架完成接口對象的生成,JDBC執行過程。
問題:
一、要執行SQL,咱們得要有DataSource,誰來持有DataSource?
二、誰來執行SQL? Configuration ?
不合適,它是配置對象,持有全部配置信息!
既然是來作事的,那就先定義一個接口吧:SqlSession
該爲它定義什麼方法呢?
需求4:框架完成接口對象的生成,JDBC執行過程。
用戶給定Mapper接口類,要爲它生成對象,用戶再使用這個對象完成對應的數據庫操做。
使用示例:
UserDao userDao = sqlSession.getMapper(UserDao.class); userDao.addUser(user);
來爲它定義一個實現類:DefaultSqlSession
它該持有什麼屬性嗎?
用戶給入一個接口類,DefaultSqlSession中就爲它生成一個對象?
萬一給入的不是一個Mapper接口呢?
也爲其生成一個對象就不合理了?
那怎麼判斷給入的接口類是不是一個Mapper接口呢?
那就只有在配置階段掃描、解析Mapper接口時作個存儲了。
存哪,用什麼存?
這也是配置信息,仍是存在Configuration 中,就用個Set來存吧。
DefaultSqlSession中須要持有Configuration
一、如何爲用戶給入的Mapper接口生成對象?
很簡單,JDK動態代理
Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, invocationHandler);
寫一版DefaultSqlSession的實現:
package com.study.leesmall.mybatis.session; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import com.study.leesmall.mybatis.config.Configuration; public class DefaultSqlSession implements SqlSession { private Configuration configuration; public DefaultSqlSession(Configuration configuration) { super(); this.configuration = configuration; } @Override public <T> T getMapper(Class<T> type) { //檢查給入的接口 if (!this.configuration.getMappers().contains(type)) { throw new RuntimeException(type + " 不在Mapper接口列表中!"); } //獲得 InvocationHandler InvocationHandler ih = null; // TODO 必需要有一個 // 建立代理對象 T t = (T)Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] {type}, ih); return t; } }
問題:每次調用getMapper(Class type)都須要生成一個新的實例嗎?
代理對象中持有InvocationHandler,若是InvocationHandler能作到線程安全,就只須要一個實例。
還得看InvocationHandler,先放這吧,把InvocationHandler搞定先
瞭解 InvocationHandler
InvocationHandler 是在代理對象中完成加強。咱們這裏經過它來執行SQL。
public interface InvocationHandler { /** @param proxy 生成的代理對象 @param method 被調用的方法 @param args @return Object 方法執行的返回值 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
來實現咱們的InvocationHandler: MapperProxy
package com.study.leesmall.mybatis.session; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MapperProxy implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO 這裏須要完成哪些事? return null; } }
思考: 在 MapperProxy.invoke方法中須要完成哪些事?
package com.study.leesmall.mybatis.session; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MapperProxy implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO 這裏須要完成哪些事? // 一、得到方法對應的SQL語句 // 二、解析SQL參數與方法參數的對應關係,獲得真正的SQL與語句參數值 // 三、得到數據庫鏈接 // 四、執行語句 // 五、處理結果 return null; } }
一、得到方法對應的SQL語句
要得到SQL語句,須要用到Configuration,MapperProxy中需持有Configuration
問題:id怎麼得來?
id是類名.方法名。從invoke方法的參數中能得來嗎?
public Object invoke(Object proxy, Method method, Object[] args)
method參數能獲得方法名,但獲得的類名不是Mapper接口類名。
那就直接讓MapperProxy持有其加強的Mapper接口類吧!簡單!
二、解析SQL參數與方法參數的對應關係,獲得真正的SQL與語句參數值
邏輯:
1)查找SQL語句中的 #{屬性} ,肯定是第幾個參數,再在方法參數中找到對應的值,存儲下來,替換 #{屬性} 爲? 。
2)查找SQL語句中的 ${屬性} ,肯定是哪一個參數,再在方法參數中找到對應的值,替換 ${屬性} 。
3)返回最終的SQL與參數數組。
解析過程涉及的數據:SQL語句、方法的參數定義、方法的參數值
@Insert("insert into t_user(id,name,sex,age) values(#{id},#{name},# {sex},#{age})") void addUser(String id,@Param("name")String xname,String sex,int age);
Parameter[] params = method.getParameters();
public Object invoke(Object proxy, Method method, Object[] args)
說明: 這裏是要肯定SQL中的?N是哪一個參數值。
這裏要分三種狀況: 方法參數是0參數,單個參數、多個參數。
0參數:不須要考慮參數了。
單個參數:SQL中的參數取值參數的屬性或就是參數自己。
多個參數:則須要肯定SQL中的參數值取第幾個參數的值。
多個參數的狀況,能夠有兩種作法:
方式一: 查找#{屬性},根據Parameter[]中的名稱(註解名、序號)匹配肯定是第幾個參數,再到 Object[] args中取到對應的值。
方式二:先將Parameter[] 和 Object[] args轉爲Map,參數名(註解名、序號)爲key,Object參數值爲值;而後再查找SQL語句中的 #{}${},根據裏面的名稱到map中取對應的值。
哪一種方式更好呢?
咱們來看下二者查找過程的輸出:
方式一相較於方式二,看起來複雜的地方是要遍歷Parameter[] 來肯定索引號。
思考一下:這種查找對應關係的事,須要每次調用方法時都作嗎?方法的參數會有不少個嗎?
這個對應關係能夠在掃描解析Mapper接口時作一次便可。在調用Mapper代理對象的方法時,
就能夠直接根據索引號去Object[] args中取參數值了。
方式2則每次調用Mapper代理對象的方法時,都須要建立轉換Map。
並且方式一,單個參數與多個參數咱們能夠一樣處理。
要在掃描解析Mapper接口時作參數解析咱們就須要定義對應的存儲結構,及修改MappedStatement了
?N--- 參數索引號 的對應關係如何表示?
?N 就是一個數值,並且是一個順序數(只是jdbc中的?是從1開始)。咱們徹底能夠用List來存儲。
參數索引號,僅僅是個索引號嗎?
@Insert("insert into t_user(id,name,sex,age,org_id) values(#{user.id},#
{user.name},#{user.sex},#{user.age},#{org.id})")
void addUser(User user,Org org);
它應該是索引號、和裏面的屬性兩部分。
解析階段由它們倆完成這件事:
咱們在MappedStatement中再增長一個方法來完成根據參數映射關係獲得真正參數值的方法:
把MapperProxy的invoke方法填填看:
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO 這裏須要完成哪些事? // 一、得到方法對應的SQL語句 String id = this.mapper.getName() + "." + method.getName(); MappedStatement ms = this.configuration.getMappedStatement(id); // 二、解析SQL參數與方法參數的對應關係,獲得真正的SQL與語句參數值 RealSqlAndParamValues rsp = ms.getRealSqlAndParamValues(args); // 三、得到數據庫鏈接 Connection conn = this.configuration.getDataSource().getConnection(); // 四、執行語句。 PreparedStatement pst = conn.prepareStatement(rsp.getSql()); // 疑問:語句必定是PreparedStatement? // 設置參數 if (rsp.getParamValues() != null) { int i = 1; for (Object p : rsp.getParamValues()) { pst.setxxx(i++, p); //這裏寫不下去了.......如何決定該調用pst的哪 個set方法? } } // 五、處理結果 return null; }
1 認識它們
JavaType:java中的數據類型。
JdbcType:Jdbc規範中根據數據庫sql數據類型定義的一套數據類型規範,各數據庫廠商遵守這套規範來提供jdbc驅動中數據類型支持。
疑問:爲何咱們在這裏須要考慮它呢?
pst.setxxx(i++, p),咱們不能根據p的類型選擇set方法嗎?
看pst的set方法中與對應的:
咱們判斷p的類型,而後選擇不可嗎? 像下面這樣
int i = 1; for (Object p : rsp.getParamValues()) { if (p instanceof Byte) { pst.setByte(i++, (Byte) p); } else if (p instanceof Integer) { pst.setInt(i++, (int) p); } else if (p instanceof String) { pst.setString(i++, (String) p); } ... else if(...) }
咱們來看一下這種狀況:
看PreparedStatment的set方法:
上面前兩種狀況怎麼處理?
這個須要用戶說明其要使用的JDBCType,否則鬼知道他想要什麼。
讓用戶怎麼指定呢?
#{user.id,jdbcType=TIME}
javaType有須要指定不呢?
好像不須要,那就暫放下。
第3種狀況怎麼處理?
這是一個未知的問題,鬼知道未來使用個人框架的人會須要怎麼處理他們的對象呢!
如何以不變應萬變呢?
面向接口編程
定義一個什麼樣的接口呢?
該接口的用途是什麼?
完成Object p 的pst.setXXX()。
下面這個if-else-if的代碼是否能夠經過TypeHandler,換成策略模式?
int i = 1; for (Object p : rsp.getParamValues()) { if (p instanceof Byte) { pst.setByte(i++, (Byte) p); } else if (p instanceof Integer) { pst.setInt(i++, (int) p); } else if (p instanceof String) { pst.setString(i++, (String) p); } ... else if(...) }
定義一些經常使用數據類型的TypeHandler.
先不急着去定義,咱們來考慮一下下面的問題。
這個怎麼使用它呢?
在MapperProxy.invoke()中?
int i = 1; for (Object p : rsp.getParamValues()) { TypeHandler th = getTypeHandler(p.getClass);//還須要別的參數嗎? th.setParameter(pst,i++,p); }
還須要JDBCType。
int i = 1; for (Object p : rsp.getParamValues()) { TypeHandler th = getTypeHandler(p.getClass,jdbcType);//jdbcType 從哪來? th.setParameter(pst,i++,p); }
問題:
一、是在invoke方法中來判斷TypeHandler呢?仍是在MappedStatement的getRealSqlAndParamValues時就返回值對應的TypeHandler?
選擇後者更合適!
那SqlAndParamValues中的參數值就不能是Object[]。它是值和TypeHandler兩部分構成。
MapperProxy中的代碼就變成下面這樣了:
int i = 1; for (ParamValue p : rsp.getParamValues()) { TypeHandler th = p.getTypeHandler() th.setParameter(pst,i++,p.getValue()); }
二、MappedStatement又從哪裏去獲取TypeHandler呢?
咱們會定義一些經常使用的,用戶可能會提供一些。用戶怎麼提供?存儲到哪裏?
Configuration 吧,它最合適了。以什麼結構來存儲呢?
這裏涉及到查找,須要根據 參數的javaType、jdbcTyp來查找。
那就定義一個map吧,以下這樣如何?
Map<Type,Map<JDBCType,TypeHandler>> typeHandlerMap;
咱們定義一個TypeHandlerRegistry類來持有全部的TypeHandler,Configuration 中則持有TypeHandlerRegistry
同時咱們完善一下TypeHandler
三、用戶如何來指定它們的TypeHandler?
在mybatis-config.xml中增長一個元素來讓用戶指定吧。
mybatis-config.dtd
<!ELEMENT configuration (mappers?, typeHandlers?)+ > <!ELEMENT mappers (mapper*,package*)> <!ELEMENT mapper EMPTY> <!ATTLIST mapper resource CDATA #IMPLIED url CDATA #IMPLIED class CDATA #IMPLIED > <!ELEMENT package EMPTY> <!ATTLIST package name CDATA #IMPLIED type CDATA #IMPLIED annotation CDATA #IMPLIED > <!ELEMENT typeHandlers (typeHandler*,package*)> <!ELEMENT typeHandler EMPTY> <!ATTLIST typeHandler class CDATA #REQUIRED >
既能夠用typeHandler指定單個,也可用package指定掃描的包,掃描包下實現了TypeHandler接口的類
mybatis-config.xml
<configuration> <mappers> <mapper resource="com/leesmall/UserMapper.xml"/> <mapper url="file:///var/mappers/CourseMapper.xml"/> <mapper class="com.study.leesmall.dao.UserDao" /> <package name="com.study.leesmall.mapper" /> <mappers> <typeHandlers> <typeHandler class="com.study.leesmall.type.XoTypeHandler" /> <package name="com.study.leesmall.type" /> </typeHandlers> </configuration>
解析註冊的工做就交給XMLConfigBuilder
四、MappedStatement中來決定TypeHandler,它就須要Configuration
五、可不能夠在解析語句參數關係時,就決定好TypeHandler?
能夠。咱們在ParameterMap中增長typeHandler屬性。
用戶在SQL語句參數中必需要指定JDBCType嗎?
經常使用的數據類型能夠不指定,咱們能夠提供默認的TypeHandler。
public class StringTypeHandler implements TypeHandler { @Override public Type getType() { return String.class; } @Override public JDBCType getJDBCType() { return JDBCType.VARCHAR; } @Override public void setParameter(PreparedStatement pst, int index, Object paramValue) throws SQLException { pst.setString(index, (String) paramValue); } }
用戶在SQL中參數定義沒有指定JDBCType,則咱們能夠直接使用咱們默認的TypeHandler
如 #{user.name}
咱們判斷它的參數類型爲String,就能夠指定它的TypeHandler爲 StringTypeHandler。可能它的數據庫類型不爲VACHAR,而是一個CHAR定長字符,不要緊!由於pst.setString對VARCHAR、CHAR是通用的。
pst.executeUpate()的返回結果是int,影響的行數。
pst.executeQuery()的返回結果是ResultSet。
在獲得SQL語句執行的結果後,要轉爲方法的返回結果進行返回。這就是執行結果處理要乾的事
根據方法的返回值類型來進行相應的處理。
這裏咱們根據SQL語句執行結果的不一樣,分開處理:
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO 這裏須要完成哪些事? // 一、得到方法對應的SQL語句 String id = this.mapper.getName() + "." + method.getName(); MappedStatement ms = this.configuration.getMappedStatement(id); // 二、解析SQL參數與方法參數的對應關係,獲得真正的SQL與語句參數值 RealSqlAndParamValues rsp = ms.getRealSqlAndParamValues(args); // 三、得到數據庫鏈接 Connection conn = this.configuration.getDataSource().getConnection(); // 四、建立語句對象。 PreparedStatement pst = conn.prepareStatement(rsp.getSql()); // 五、設置語句參數 int i = 1; for (ParamValue p : rsp.getParamValues()) { TypeHandler th = p.getTypeHandler() th.setParameter(pst,i++,p.getValue()); } // 六、執行語句並處理結果 switch (ms.getSqlCommandType()) { case INSERT: case UPDATE: case DELETE: int rows = pst.executeUpdate(); return handleUpdateReturn(rows, ms, method); case SELECT: ResultSet rs = pst.executeQuery(); return handleResultSetReturn(rs, ms, method); } } private Object handleUpdateReturn(int rows, MappedStatement ms, Method method) { // TODO Auto-generated method stub return null; } private Object handleResultSetReturn(ResultSet rs, MappedStatement ms, Method method) { // TODO Auto-generated method stub return null; }
pst.executeUpate()的返回結果是int
方法的返回值能夠是什麼?
void、int、long 、 其餘的不能夠!
private Object handleUpdateReturn(int rows, MappedStatement ms, Method method) { Class<?> returnType = method.getReturnType(); if (returnType == Void.TYPE) { return null; } else if (returnType == int.class || returnType == Integer.class) { return rows; } else if (returnType == long.class || returnType == Long.class) { return (long) rows; } throw new IllegalArgumentException("update類方法的返回值只能是:void/int/Integer/long/Long"); }
pst.executeQuery()的返回結果是ResultSet
方法的返回值能夠是什麼?
能夠是void、單個值、集合。
單個值能夠是什麼類型的值?
任意值、(map)
@Select("select count(1) from t_user where sex = #{sex}") int query(String sex);
@Select("select id,name,sex,age,address from t_user where id = #{id}") User queryUser(String id);
@Select("select id,name,sex,age,address from t_user where id = #{id}") Map queryUser1(String id);
集合能夠是什麼類型?
List、Set、數組、Vector
@Select("select id,name,sex,age,address from t_user where sex = #{sex}
order by #{orderColumn}")
List<User> query(String sex, String orderColumn);
@Select("select id,name,sex,age,address from t_user where sex = #{sex}
order by #{orderColumn}")
List<Map> query1(String sex, String orderColumn);
集合的元素能夠是什麼類型的?
任意類型的,集合只是單個值多作幾遍。
結果集中的列如何與結果、結果的屬性對應?
根據結果集列名與屬性名對應
若是屬性名與列名不同呢?
則需用戶顯式說明映射規則。
須要考慮JDBCType --- JavaType的處理嗎?
不管結果是什麼類型的,在這裏咱們都是要完成一件事:從查詢結果中得到數據返回,只是返回類型不一樣,有不一樣的獲取數據的方式。
請思考:如何讓下面這個方法的代碼的寫好後再也不改變?
private Object handleResultSetReturn(ResultSet rs, MappedStatement ms, Object[] args) { // TODO Auto-generated method stub return null; }
我須要在此作個抽象,應用策略模式,不一樣的處理實現這個抽象接口。
那麼在handleResultSetReturn()方法中咱們從哪獲得ResultHandler呢?
從MappedStatement 中獲取,每一個語句對象(查詢類型的)中都持有它對應的結果處理器。
在解析準備MappedStatement對象時根據方法的返回值類型選定對應的ResultHandler。
在handleResultSetReturn方法中只需調用ms中的ResultHandler:
private Object handleResultSetReturn(ResultSet rs, MappedStatement ms, Object[] args) throws Throwable { return ms.getResultHandler().handle(rs, args); }
@Select("select count(1) from t_user where sex = #{sex}") int query(String sex); @Select("select id,name,sex,age,address from t_user where id = #{id}") User queryUser(String id); @Select("select id,name,sex,age,address from t_user where id = #{id}") Map queryUser1(String id);
一、基本數據類型、String 如何處理?
針對這種狀況,提供對應的ResultHandler實現:
SimpleTypeResultHandler中須要定義什麼屬性?
handle方法中的邏輯該是怎樣的?
public Object handle(ResultSet rs, Object[] args) throws Throwable { //從rs中取對應值 return rs.getXXX(OOO); }
問題1:該調用rs的哪一個get方法?
得根據返回值來,返回值類型從哪來?
從SimpleTypeResultHandler中取,在建立MappedStatement時,根據反射得到的返回值類型給入到SimpleTypeResultHandler中。
SimpleTypeResultHandler的handle方法中的代碼邏輯以下:
private Object handle(ResultSet rs, Object[] args) throws Throwable { Class<?> returnType = method.getReturnType(); if (returnType == short.class || returnType == Short.class) { return rs.getShort(xxx); } else if (returnType == int.class || returnType == Integer.class) { return rs.getInt(xxx); } else if (returnType == long.class || returnType == Long.class) { return rs.getLong(xxx); } ... return null;
問題2:該取結果集中的哪一列?
若是結果集中只有一列:那就取第1列。
若是結果集中是有多列呢?
問題:結果集中應不該該有多列?
兩種方案:
一、該返回值狀況下不容許結果集多列。
二、不限制,用戶指定列名。
問題3:這麼多if else 合適嗎?
不合適,咋辦?策略模式
該定義怎樣的策略?
這是要作什麼事情?
從結果集中獲取值,跟pst.setXXX同樣。
可不能夠在TypeHandler中加方法?
public interface TypeHandler<T> { Type getType(); JDBCType getJDBCType(); void setParameter(PreparedStatement pst, int index, Object paramValue) throws SQLException; T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; }
public class StringTypeHandler implements TypeHandler<String> { @Override public Type getType() { return String.class; } @Override public JDBCType getJDBCType() { return JDBCType.VARCHAR; } @Override public void setParameter(PreparedStatement pst, int index, Object paramValue) throws SQLException { pst.setString(index, (String) paramValue); } @Override public String getResult(ResultSet rs, String columnName) throws SQLException { return rs.getString(columnName); } @Override public String getResult(ResultSet rs, int columnIndex) throws SQLException { return rs.getString(columnIndex); } }
同樣的,在啓動解析階段完成結果的TypeHandler選定。
根據返回值類型,從TypeHandlerRegistry中取,要取,還得有JDBCType,用戶能夠指定,也可不指定,不指定則使用默認的該類型的TypeHandler。
默認TypeHandler如何註冊,修改registerTypeHandler方法的定義:
registerTypeHandler(TypeHandler th,boolean defalut){ Map<JDBCType,TypeHandler> cmap = typeHandlerMap.get(th.getType); if(cmap == null) { cmap = new HashMap<JDBCType,TypeHandler>(); typeHandlerMap.put(th.getType,cmap); } camp.put(th.getJDBCType(),th); if(default) { cmap.put(DefaultJDBCType.class/null,th); } }
很好,那就能夠在SimpleTypeResultHandler中持有對應的TypeHandler。
問:在SimpleTypeResultHandler中還有必要持有Class<?> returnType嗎?
不須要,在TypeHandler中有了。
SimpleTypeResultHandler 的handle方法代碼就簡單了:
public Object handle(ResultSet rs, Object[] args) throws Throwable { if (StringUtils.isNotEmpty(columnName)) { return typeHandler.getResult(rs, columnName); } else { return typeHandler.getResult(rs, columnIndex); } }
@Select("select id,name,sex,age,address from t_user where id = #{id}")
User queryUser(String id);
分析:
一、要完成的事情是什麼?
建立對象
從結果集中取數據給到對象
問題:
一、如何建立對象?
反射調用構造方法。
構造方法有多種狀況:
1 未顯式定義構造方法
public class User { private String id; private String name; private String sex; ... public String getId() { return id; } public void setId(String id) { this.id = id; } ... }
這種狀況不須要考慮什麼,直接建立對象!
2 顯式定義了一個構造方法
public class User { private String id; private String name; private String sex; ... public User(String id, String name, String sex) { super(); this.id = id; this.name = name; this.sex = sex; } public String getId() { return id; } public void setId(String id) { this.id = id; } ... }
此種狀況下,要建立對象,則須要對應的構造參數值。
問題1:構造參數值從哪來?
ResultSet
問題2:怎麼知道該從ResultSet中取哪一個列的值,取什麼類型的值?
得定義構造參數與ResultSet中列的對應規則,下面的規則是否能夠?
一、優先採用指定列名的方式:用參數名稱當列名、或用戶爲參數指定列名(參數名與列名不
一致時、取不到參數名時);
二、如不能取得參數名,則按參數順序來取對應順序的列。
問題3:用戶如何來指定列名?
註解、xml配置
public User(@Arg(column="id")String id, @Arg(column="xname")String name, @Arg(column="sex")String sex) { super(); this.id = id; this.name = name; this.sex = sex; }
@Documented @Retention(RUNTIME) @Target(PARAMETER) public @interface Arg { String name() default "";
String column() default "";
Class<?> javaType() default void.class;
JdbcType jdbcType() default JdbcType.UNDEFINED;
Class<? extends TypeHandler> typeHandler() default UndefinedTypeHandler.class; }
<resultMap id="User" type="com.study.leesmall.mybatis.sample.model.User"> <constructor> <arg name="" column="" JdbcType="" javaType="" typeHandler=""/> </constructor> </resultMap>
mybatis-mapper.dtd 中增長以下定義
<!ELEMENT resultMap (constructor?)> <!ATTLIST resultMap id CDATA #REQUIRED type CDATA #REQUIRED > <!ELEMENT constructor (arg*)> <!ELEMENT arg EMPTY> <!ATTLIST arg javaType CDATA #IMPLIED column CDATA #IMPLIED jdbcType CDATA #IMPLIED typeHandler CDATA #IMPLIED name CDATA #IMPLIED >
問題4:這些映射信息獲得後如何表示、存儲?
定義一個結果映射實體:ResultMap
注意,在建立ResultMap時,當用戶沒有指定TypeHandler或是UndefinedTypeHandler時,要根據type、jdbcType取對應的typeHandler,沒有則爲null;
問題五、ResultMap 元素怎麼表示?
ResultMap類定義自己就是表示一種java類型與JDBCType類型的映射,基本數據類型與複合類型(類)都是java類型。
擴充一下ResultMap便可:
注意:這裏有個使用規則須要注意一下:
若是ResultMap中有TypeHandler,則該結果直接經過調用TypeHandler來得到。沒有TypeHandler時則看有constructorResultMaps沒,有則根據此取結果集中的值來調用對應的構造方法建立對象。
3 定義了多個構造方法,怎麼辦?
public class User { private String id; private String name; private String sex; ... public User(String id, String name, String sex) { super(); this.id = id; this.name = name; this.sex = sex; } public User(String id, String name, String sex, int age) { super(); this.id = id; this.name = name; this.sex = sex; this.age = age; } public String getId() { return id; } public void setId(String id) { this.id = id; } ... }
用戶指定構造方法,沒有指定時則用默認構造方法(沒有則報錯)。
用戶怎麼指定:
註解 :
@MapConstructor public User(@Arg("id")String id, @Arg("xname")String name, @Arg("sex")String sex) { super(); this.id = id; this.name = name; this.sex = sex; }
/** * 標識選用的構造方法 */ @Documented @Retention(RUNTIME) @Target(CONSTRUCTOR) public @interface MapConstructor { }
xml:根據constructor元素中 arg元素的數量、javaType來肯定構造函數。注意arg有順序規則、必須指定構造方法的所有參數。
<resultMap id="User" type="com.study.leesmall.mybatis.sample.model.User"> <constructor> <arg column="id" javaType="String"/> <arg column="name" javaType="String"/> <arg column="sex" javaType="String"/> </constructor> </resultMap>
建立出對象後,能夠從結果集中取值來填充對象的屬性。
問題1:該給哪些屬性賦值?
能夠有兩種規則:
一、用戶指定要給哪些屬性賦值。
二、自動映射賦值:取列的值賦給同名的屬性。
二者能夠一塊兒使用。
那麼這裏就涉及兩個事情:
一、用戶如何指定?
註解方式: 咱們給定義一個註解 @Result
public class User { @Result private String id; @Result(column="xname") private String name; ... }
@Documented @Retention(RUNTIME) @Target({ TYPE, FIELD }) public @interface Result { String column() default ""; Class<?> javaType() default void.class; JdbcType jdbcType() default JdbcType.UNDEFINED; Class<? extends TypeHandler> typeHandler() default UndefinedTypeHandler.class; }
xml方式:
<resultMap id="User" type="com.study.leesmall.mybatis.sample.model.User"> <constructor> <arg column="id" javaType="String"/> <arg column="name" javaType="String"/> <arg column="sex" javaType="String"/> </constructor> <result property="age" column="age" /> </resultMap>
mybatis-mapper.dtd
<!ELEMENT resultMap (constructor?,result*)> <!ATTLIST resultMap id CDATA #REQUIRED type CDATA #REQUIRED > <!ELEMENT constructor (arg*)> <!ELEMENT arg EMPTY> <!ATTLIST arg javaType CDATA #IMPLIED column CDATA #IMPLIED jdbcType CDATA #IMPLIED typeHandler CDATA #IMPLIED name CDATA #IMPLIED > <!ELEMENT result EMPTY> <!ATTLIST result property CDATA #IMPLIED javaType CDATA #IMPLIED column CDATA #IMPLIED jdbcType CDATA #IMPLIED typeHandler CDATA #IMPLIED >
問題:這些信息如何表示、存儲?
二、是否自動映射如何指定?
增長一個屬性便可:
autoMapping="true"
<resultMap id="User" type="com.study.leesmall.mybatis.sample.model.User" autoMapping="true"> <result property="age" column="age" /> </resultMap>
<!ELEMENT resultMap (constructor?,result*)> <!ATTLIST resultMap id CDATA #REQUIRED type CDATA #REQUIRED autoMapping (true|false) #IMPLIED >
註解方式:
/** 標識類對象要進行自動映射 */ @Documented @Retention(RUNTIME) @Target({ TYPE, FIELD }) public @interface AutoMapping { }
@AutoMapping public class User { @Result private String id; @Result(column="xname") private String name; ... }
爲方便統一開啓自動映射,咱們能夠在Configuration中設計一個全局配置參數,具體的能夠覆蓋全局的。
在哪可配置它?
在mybatis-config.xml中增長一個配置項便可。
<configuration> <settings> <setting name="autoMappingBehavior" value="PARTIAL"/> </settings> </configuration>
對象中包對象是個問題,先把問題搞清楚,看下面的語句示例:
<!-- Very Complex Statement --> <select id="selectBlogDetails" resultMap="detailedBlogResultMap"> select B.id as blog_id, B.title as blog_title, B.author_id as blog_author_id, A.id as author_id, A.username as author_username, A.password as author_password, A.email as author_email, A.bio as author_bio, A.favourite_section as author_favourite_section, P.id as post_id, P.blog_id as post_blog_id, P.author_id as post_author_id, P.created_on as post_created_on, P.section as post_section, P.subject as post_subject, P.draft as draft, P.body as post_body from Blog B left outer join Author A on B.author_id = A.id left outer join Post P on B.id = P.blog_id where B.id = #{id} </select>
再看類
public class Blog { private String id; private String title; private Author author; private List<Post> posts; .... } public class Author { private String id; private String username; ... } public class Post { private String id; private String subject; ... }
這就是對象中包含對象,要從查詢結果中獲得Blog,Blog的Author posts數據。
這就是ORM中的關係映射問題。
結果的映射是簡單的,由於就是指定裏面的屬性取哪一個列的值。
public class Blog { @Result(column="blog_id") private String id; @Result(column="blog_title") private String title; @Result private Author author; @Result private List<Post> posts; .... } public class Author { @Result(column="author_id") private String id; @Result(column="author_username") private String username; ... } public class Post { @Result(column="post_id") private String id; @Result(column="post_subject") private String subject; ... }
咱們的ResultMap類也是支持的:
可是從結果集中取值來填裝對象則是複雜的!
請先看查詢的結果示例:
while(rs.next()){ }
複雜點:不是一行一個Blog對象,處理行時要判斷該行的blog是否已取過了。
問題核心點在哪?
當我操做一行,如何判斷該行的Blog已經取過沒?
這就要求要知道區分Blog的惟一標識、區分Author的惟一標識。怎麼知道?
用戶得告訴咱們他們的id屬性是哪一個,對應的列是哪一個。
讓用戶怎麼來指定id屬性呢?
註解方式:在@Arg 、@Rersult註解中增長id指定項。
@Documented @Retention(RUNTIME) @Target(PARAMETER) public @interface Arg { boolean id() default false; String name() default ""; String column() default ""; Class<?> javaType() default void.class; JdbcType jdbcType() default JdbcType.UNDEFINED; Class<? extends TypeHandler> typeHandler() default UndefinedTypeHandler.class; }
@Documented @Retention(RUNTIME) @Target({ TYPE, FIELD }) public @interface Result { boolean id() default false; String column() default ""; Class<?> javaType() default void.class; JdbcType jdbcType() default JdbcType.UNDEFINED; Class<? extends TypeHandler> typeHandler() default UndefinedTypeHandler.class; }
xml方式增長:增長argId、id元素
<resultMap id="detailedBlogResultMap" type="Blog"> <constructor> <idArg column="blog_id" javaType="int"/> </constructor> .... </resultMap> <resultMap id="AuthorMap" type="Author"> <id property="id" column="author_id"/> <result property="username" column="author_username"/> <result property="password" column="author_password"/> </resultMap>
在ResultMap中增長ID信息
問題:要體現出一對一,一對多關係嗎?咱們會在哪裏須要知道這個關係?
看一個mybatis中的複雜xml ResultMap示例:
<!-- 超複雜的 Result Map --> <resultMap id="detailedBlogResultMap" type="Blog"> <constructor> <idArg column="blog_id" javaType="int"/> </constructor> <result property="title" column="blog_title"/> <association property="author" javaType="Author"> <id property="id" column="author_id"/> <result property="username" column="author_username"/> <result property="password" column="author_password"/> <result property="email" column="author_email"/> <result property="bio" column="author_bio"/> <result property="favouriteSection" column="author_favourite_section"/> </association> <collection property="posts" ofType="Post"> <id property="id" column="post_id"/> <result property="subject" column="post_subject"/> <association property="author" javaType="Author"/> <collection property="comments" ofType="Comment"> <id property="id" column="comment_id"/> </collection> </collection> </resultMap>
知道惟一標識了,要判斷前面是否取過了,則還須要有個上下文持有取到的對象,並能根據id列值取到對應的對象。
爲對象類型返回結果定義一個ResultHandler實現ClassTypeResultHandler:
@Select("select id,name,sex,age,address from t_user where id = #{id}")
Map queryUser1(String id);
不能在解析階段得到ResultMap
當執行完第一次查詢就能夠肯定下來
咱們從結果集中能獲得的是JDBCType
問題:
一、key 用什麼?
用列名
二、取成什麼java類型的值?
JDBCType中根據整型類型值得到對應的JDBCType
/** * Returns the {@code JDBCType} that corresponds to the specified * {@code Types} value * @param type {@code Types} value * @return The {@code JDBCType} constant * @throws IllegalArgumentException if this enum type has no constant with * the specified {@code Types} value * @see Types */ public static JDBCType valueOf(int type) { for( JDBCType sqlType : JDBCType.class.getEnumConstants()) { if(type == sqlType.type) return sqlType; } throw new IllegalArgumentException("Type:" + type + " is not a valid "+ "Types.java value."); }
TypeHandler ---> javaType
在TypehandlerRegistry中定義一個JDBCType類型對應的默認的TypeHandler集合,來完成取java值放入到Map中
第一次處理結果時,要把這個ResultMaps填充好,後需查詢結果的處理就是直接使用resultMaps
返回集合就是單個的重複
if(method.getReturnType() == List.class) { Type genericType = method.getGenericReturnType(); if(genericType == null) { // 當集合中放Map } else if (genericType instanceof ParameterizedType) { ParameterizedType t = (ParameterizedType) genericType; Class<?> elementType = (Class<?>)t.getActualTypeArguments()[0]; } }