MyBatis是一款支持普通SQL查詢、存儲過程和高級映射的持久層框架。MyBatis消除了幾乎全部的JDBC代碼、參數的設置和結果集的檢索。MyBatis可使用簡單的XML或註解用於參數配置和原始映射,將接口和Java POJO(普通Java對象)映射成數據庫中的記錄。html
Mybatis能夠把Mapper.xml文件直接映射到對應的接口,調用接口方法會自動去Mapper.xml文件中找到對應的標籤,這個功能就是利用java的動態代理在binding包中實現的。java
如下面的配置文件爲例:sql
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xrq.StudentMapper">
<select id="selectStudentById" parameterType="int" resultType="Student">
<![CDATA[
select * from student where studentId = #{id}
]]>
</select>
</mapper>數據庫
使用<![CDATA[ ... ]]>,能夠保證不管如何<![CDATA[ ... ]]>裏面的內容都會被解析成SQL語句。所以,建議每一條SQL語句都使用<![CDATA[ ... ]]>包含起來,這也是一種規避錯誤的作法。緩存
<select id="selectStudentById" parameterType="int" resultType="Student">
<![CDATA[
select * from student where studentId = #{id}
]]>
</select>mybatis
resultType只能是一個實體類,參數要和實體類裏面定義的同樣,好比studentId、studentName,MyBatis將會自動查找這些屬性,而後將它們的值傳遞到預處理語句的參數中去。 app
使用參數的時候使用了」#」,另外還有一個符號」$」也能夠引用參數,使用」#」最重要的做用就是防止SQL注入。框架
調用該語句的Java代碼的寫法:fetch
public Student selectStudentByIdAndName(int studentId, String studentName)
{
SqlSession ss = ssf.openSession();
Student student = null;
try
{
student = ss.selectOne("com.xrq.StudentMapper.selectStudentByIdAndName",
new Student(studentId, studentName, 0, null));
}
finally
{
ss.close();
}
return student;
}ui
<select id="selectAll" parameterType="int" resultType="Student" flushCache="false" useCache="true" timeout="10000" fetchSize="100" statementType="PREPARED" resultSetType="FORWARD_ONLY">
<![CDATA[
select * from student where studentId > #{id};
]]>
</select>
屬性的做用:
id—-用來和namespace惟一肯定一條引用的SQL語句
parameterType—-參數類型,若是SQL語句中的動態參數只有一個,這個屬性無關緊要
resultType—-結果類型,注意若是返回結果是集合,應該是集合所包含的類型,而不是集合自己
flushCache—-將其設置爲true,不管語句何時被調用,都會致使緩存被清空,默認值爲false
useCache—-將其設置爲true,將會致使本條語句的結果被緩存,默認值爲true
timeout—-這個設置驅動程序等待數據庫返回請求結果,並拋出異常事件的最大等待值,默認這個參數是不設置的(即由驅動自行處理)
fetchSize—-這是設置驅動程序每次批量返回結果的行數,默認不設置(即由驅動自行處理)
statementType—-STATEMENT、PREPARED或CALLABLE的一種,這會讓MyBatis選擇使用Statement、PreparedStatement或CallableStatement,默認值爲PREPARED。這個相信大多數朋友本身寫JDBC的時候也只用過PreparedStatement
resultSetType—-FORWARD_ONLY、SCROLL_SENSITIVE、SCROLL_INSENSITIVE中的一種,默認不設置(即由驅動自行處理)
調用sql語句的Java程序:
public List<Student> selectStudentsById(int studentId)
{
SqlSession ss = ssf.openSession();
List<Student> list = null;
try
{
list = ss.selectList("com.xrq.StudentMapper.selectAll", studentId);
}
finally
{
ss.close();
}
return list;
}
使用resultType的方式是有前提的,那就是假定列名和Java Bean中的屬性名存在對應關係,若是名稱不對應,可使用resultMap來解決列名不匹配的問題:
<resultMap type="Student" id="studentResultMap">
<id property="studentId" column="studentId" />
<result property="studentName" column="studentName" />
<result property="studentAge" column="studentAge" />
<result property="studentPhone" column="studentPhone" />
</resultMap>
<select id="selectAll" parameterType="int" resultMap="studentResultMap" flushCache="false" useCache="true"
timeout="10000" fetchSize="100" statementType="PREPARED" resultSetType="FORWARD_ONLY">
<![CDATA[
select * from student where studentId > #{id};
]]>
</select>
這樣就能夠了,注意兩點:
一、resultMap定義中主鍵要使用id
二、resultMap和resultType不能夠同時使用
<insert id="insertOneStudent" parameterType="Student">
<![CDATA[
insert into student values(null, #{studentName}, #{studentAge}, #{studentPhone});
]]>
</insert>
<insert id="insertOneStudent" parameterType="Student" useGeneratedKeys="true" keyProperty="studentId">
<![CDATA[
insert into student(studentName, studentAge, studentPhone)
values(#{studentName}, #{studentAge}, #{studentPhone});
]]>
</insert>
useGeneratedKeys表示讓數據庫自動生成主鍵,keyProperty表示生成主鍵的列。
Java調用代碼:
public void insertOneStudent(String studentName, int studentAge, String studentPhone)
{
SqlSession ss = ssf.openSession();
try
{
ss.insert("com.xrq.StudentMapper.insertOneStudent",
new Student(0, studentName, studentAge, studentPhone));
ss.commit();
}
catch (Exception e)
{
ss.rollback();
}
finally
{
ss.close();
}
}
update:
<update id="updateStudentAgeById" parameterType="Student">
<![CDATA[
update student set studentAge = #{studentAge} where
studentId = #{studentId};
]]>
</update>
delete:
<delete id="deleteStudentById" parameterType="int">
<![CDATA[
delete from student where studentId = #{studentId};
]]>
</delete>
update的Java代碼:
public void updateStudentAgeById(int studentId, int studentAge)
{
SqlSession ss = ssf.openSession();
try
{
ss.update("com.xrq.StudentMapper.updateStudentAgeById",
new Student(studentId, null, studentAge, null));
ss.commit();
}
catch (Exception e)
{
ss.rollback();
}
finally
{
ss.close();
}
}
studentId和studentAge必須是這個順序,互換位置就更新不了學生的年齡了。
如上面介紹的配置方式:
<mapper namespace="com.test.dao.EmployeeMapper">
<select id="getEmpById" resultType="com.test.beans.Employee">
select id,last_name lastName,email,gender from tb1_emplyee where id = #{id}
</select>
</mapper>
使用Mapper標籤進行綁定:
<mappers>
<mapper resource="EmployeeMapper.xml" />
</mappers>
定義一個接口:
public interface EmployeeMapperAnnotation {
@Select("select id,last_name lastName,email,gender from tb1_emplyee where id = #{id}")
public Employee getEmpById(Integer id);
}
而後在全局配置文件中進行註冊:
<mappers>
<mapper class="com.test.dao.EmployeeMapperAnnotation"></mapper>
</mappers>
<mappers>
<package name="com.atguigu.mybatis.dao"/>
</mappers>
【注】映射文件名必須和接口同名,而且放在與接口同一目錄下,否則註冊不了。
以上是簡單介紹,具體映射文件的配置見詳解Java的MyBatis框架中SQL語句映射部分的編寫。
Mybatis在初始化時會把獲取到的Mapper接口註冊到MapperRegistry,註冊的時候建立一個Mapper代理工廠,這個工廠經過JDK的代理建立一個執行對象,建立代理須要的InvocationHandler爲MapperProxy。
//接口註冊
public class MapperRegistry {
public <T> void addMapper(Class<T> type) {
//若是是接口
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//放到map中, value爲建立代理的工廠
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the mapper parser. If the type is already known, it won't try.
//這裏是解析Mapper接口裏面的註解
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
mybatis使用XMLMapperBuilder類的實例來解析mapper配置文件。
從knownMappers中根據接口類型取出對應的代理建立工廠,用該工廠建立代理。
public class MapperRegistry {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//取出MapperProxyFactory
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null)
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
try {
//建立代理
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
}
//建立代理的工廠
public class MapperProxyFactory<T> {
/**
* 須要建立代理的接口
*/
private final Class<T> mapperInterface;
/**
* 執行方法的緩存,不須要每次都建立MapperMethod
*/
private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//建立代理, InvocationHanderl是MapperProxy
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface },mapperProxy);
}
/**
* 傳人sqlSession建立代理
* @param sqlSession
* @return
*/
public T newInstance(SqlSession sqlSession) {
//把代理執行須要用到的對象傳入
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
調用代理方法會進入到MapperProxy的public Object invoke(Object proxy, Method method, Object[] args)方法
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//若是方法是Object裏面的則直接調用方法
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//獲取執行方法的封裝對象
final MapperMethod mapperMethod = cachedMapperMethod(method);
//裏面就是找到對應的sql 執行sql語句
return mapperMethod.execute(sqlSession, args);
}
//緩存, 不須要每次都建立
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
//傳人配置參數
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
最終執行sql會進入到MapperMethod中execute方法:
//具體的根據接口找到配置文件標籤的類
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
//SqlCommand封裝該接口方法須要執行sql的相關屬性,如:id(name), 類型
this.command = new SqlCommand(config, mapperInterface, method);
//執行方法特性進行封裝,用於構造sql參數,判斷執行sql邏輯走哪條分支
this.method = new MethodSignature(config, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//先找到對應的執行sql類型, sqlSession會調用不一樣方法
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {//若是是查詢, 須要對返回作判斷處理
//根據方法的特性判斷進入哪一個執行分支
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
//只查一條數據
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType()
+ ").");
}
return result;
}
}
mapperProxyBuilder建立流程:
mapperProxy建立流程:
sql語句執行流程:
http://www.cnblogs.com/dongying/p/4142476.html