MyBatis原理淺析
簡介
MyBatis是一個輕量級的ORM框架,它簡化了對關係資料庫的使用,開發人員可以在XML或註解中編寫SQL來完成對資料庫的操作。
如果完全使用XML方式,SQL語句可以集中維護,做到與Java程式碼完全隔離,便於對SQL調優。
原理及流程
- 載入配置:配置來源於兩個地方,一是配置檔案,一是Java程式碼的註解,將SQL的配置資訊載入成為一個個MappedStatement物件(包括了傳入引數對映配置、執行的SQL語句、結果對映配置),儲存在記憶體中。
- SQL解析:當API介面層接收到呼叫請求時,會接收到傳入SQL的ID和傳入物件(可以是Map、JavaBean或者基本資料型別),Mybatis會根據SQL的ID找到對應的MappedStatement,然後根據傳入引數物件對MappedStatement進行解析,解析後可以得到最終要執行的SQL語句和參。
- SQL執行:將最終得到的SQL和引數拿到資料庫進行執行,得到操作資料庫的結果。
- 結果對映:將操作資料庫的結果按照對映的配置進行轉換,可以轉換成HashMap、JavaBean或者基本資料型別,並將最終結果返回。
技術盞
反射、jdk動態代理、Ognl表示式引擎、快取
反射和動態代理
解析是將SQL或儲存過程定義表述為Mybatis中對應物件的過程,例如將執行的Sql標籤( , …)解析為 MappedStatement ;輸入引數定義標籤解析為 ParameterMap ;結果列定義標籤解析為 ResultMap
Mybatis支援將一個類的方法對映到一個 mapper 檔案裡的對應 statement sql,將方法名與DML SQL標籤的id對應起來,這樣我們就可以透明地使用 interface 的方式結合了面向物件的方式來與資料庫操作,這樣做更趨近於面向物件的程式設計風格。其中用到jdbc動態代理原理,用 MapperProxy 動態代理了需要執行的介面方法,主要代理邏輯在 MapperMethod 中實現,負責用介面的名稱以及方法名稱找到解析好的 MappedStatement 然後呼叫 SqlSession 中對應的執行邏輯執行。
快取
Mybatis的一級快取是SqlSession級別。第一次執行select時候會發現sqlsession快取沒有記錄,會去資料庫查詢,然後把結果儲存到快取,第二次同等條件查詢下,就會從快取中查詢到結果。另外為了避免髒讀,每次執行更新新增刪除時候會清空當前sqlsession快取。
二級快取是namespace級別的。同一個namespace下的搜尋語句共享一個二級快取。如果開啟了二級快取,則先從二級快取中查詢,查詢不到則委託為SimpleExecutor查詢,而它則會先從一級快取中查詢,查詢不到則從資料庫查詢。
mybaits的二級快取一般不怎麼使用,預設一級快取是開啟的。
基於註解方式的demo
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_name` varchar(100) DEFAULT NULL,
`create_time` int(11) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.4.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
application.properties
project.name=mybatis
server.port=7005
management.port=7006
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=123
logging.level.root=info
mybatis.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 我們不會將這個Bean進行例項化,class屬性中定義了PropertyPlaceholderConfigurer類,可以告訴Spring我們實際上要註冊一個properties檔案-->
<bean id="propertyFileConfigForDB"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>classpath:application.properties</value>
</property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 宣告式事物控制 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- spring和MyBatis完美整合,不需要mybatis的配置對映檔案 -->
<bean id="sqlSessionFactor" class="org.mybatis.spring.SqlSessionFactoryBean" primary="true">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- DAO介面所在包名,Spring會自動查詢其下的類 ,包下的類需要使用@MapperScan註解,否則容器注入會失敗 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.test.dao.*"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactor"/>
</bean>
</beans>
主類上添加註解 @ImportResource({“classpath:mybatis.xml”}),以引入配置mybatis.xml
public class UserDo {
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
private Long id;
private String user_name;
private Long create_time;
private Integer age;
public UserDo() {}
public String getUser_name() {
return user_name;
}
public void setUser_name(String user_name) {
this.user_name = user_name;
}
public Long getCreate_time() {
return create_time;
}
public void setCreate_time(Long create_time) {
this.create_time = create_time;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
@MapperScan
public interface UserMapper {
@Insert("insert into user (user_name,create_time,age) values ("
+ "#{user_name},#{create_time},#{age})")
@Options(useGeneratedKeys = true, keyProperty = "id")
Long insertUser(UserDo userDo);
@Delete("delete from user where id = #{id}")
Long deleteUser( Long id);
@Update("update user set user_name=#{user_name}, create_time=#{create_time},"
+ " age=#{age} where id=#{id}")
Long updateUser(UserDo userDo);
@Select("select * from user where id=#{id}")
UserDo selectUserById(Long id);
}
public interface UserService {
Long createUser(User user);
Long deleteUser(Long id);
Long updateUser(User user);
User selectUser(Long id);
}
@Service
public class UserServiceImpl implements UserService {
private static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
@Resource
private UserMapper userMapper;
@Override
public Long createUser(User user) {
try {
return userMapper.insertUser(new UserDo(user));
} catch (Exception ex) {
logger.error("exception occurred: {}", ex);
}
return null;
}
@Override
public Long deleteUser(Long id) {
try {
return userMapper.deleteUser(id);
} catch (Exception ex) {
logger.error("exception occurred: {}", ex);
}
return null;
}
@Override
public Long updateUser(User user) {
try {
return userMapper.updateUser(new UserDo(user));
} catch (Exception ex) {
logger.error("exception occurred: {}", ex);
}
return null;
}
@Override
public User selectUser(Long id){
try {
UserDo userDo = userMapper.selectUserById(id);
return new User(userDo);
} catch (Exception ex) {
logger.error("exception occurred: {}", ex);
}
return null;
}
}