1.1mybatis下載
mybaits 的代碼由github.com 管理,
地址:https://github.com/mybatis/mybatis-3/releases
mybatis-3.4.6.jar----mybatis 的核心包
lib----mybatis 的依賴包
mybatis-3.4.6.pdf----mybatis 使用手冊
1.2創(chuàng)建mysql 數(shù)據(jù)庫
1.3Mybatis 入門程序
1.3.1需求
實現(xiàn)以下功能:
根據(jù)用戶id 查詢一個用戶信息
根據(jù)用戶名稱模糊查詢用戶信息列表
添加用戶
更新用戶
刪除用戶
1.3.2 第一步:創(chuàng)建java 工程
使用eclipse 創(chuàng)建java 工程,jdk 使用jdk1.8.0_144
1.3.3 第二步:加入jar 包
加入mybatis 核心包、依賴包、數(shù)據(jù)驅(qū)動包。
1.3.4 第三步:log4j.properties
在classpath 下創(chuàng)建log4j.properties
如下:
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
mybatis 默認使用log4j 作為輸出日志信息。
1.3.5 第四步:SqlMapConfig.xml
在classpath 下創(chuàng)建SqlMapConfig.xml,如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 和spring整合后environments配置將廢除 -->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事務管理 -->
<transactionManager type="JDBC" />
<!-- 數(shù)據(jù)庫連接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="mysql" />
</dataSource>
</environment>
</environments>
</configuration>
SqlMapConfig.xml 是mybatis 核心配置文件,上邊文件的配置內(nèi)容為數(shù)據(jù)源、事務管理。
1.3.6 第五步:po 類
Po 類作為mybatis 進行sql 映射使用,po 類通常與數(shù)據(jù)庫表對應,User.java 如下:
package com.ghw.po;
import java.util.Date;
public class User {
private int id;
private String username;// 用戶姓名
private String sex;// 性別
private Date birthday;// 生日
private String address;// 地址
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
1.3.7 第六步:程序編寫
1.3.7.1查詢
1.3.7.1.1 映射文件:
在classpath 下的sqlmap 目錄下創(chuàng)建sql 映射文件Users.xml:
<?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="test">
</mapper>
namespace :命名空間,用于隔離sql 語句,后面會講另一層非常重要的作用。
在Users.xml 中添加:
<mapper namespace="test">
<!-- 根據(jù)id獲取用戶信息 -->
<select id="findUserById" parameterType="int"
resultType="com.ghw.po.User">
select * from user where id = #{id}
</select>
<!-- 自定義條件查詢用戶列表 -->
<select id="findUserByUsername" parameterType="java.lang.String"
resultType="cn.itcast.mybatis.po.User">
select * from user where username like '%${value}%'
</select>
</mapper>
parameterType:定義輸入到sql 中的映射類型,#{id}表示使用preparedstatement 設
置占位符號并將輸入變量id 傳到sql。
resultType:定義結(jié)果映射類型。
1.3.7.1.2 加載映射文件
mybatis 框架需要加載映射文件,將Users.xml 添加在SqlMapConfig.xml,如下:
<mappers>
<mapper resource="sqlmap/User.xml" />
</mappers>
1.3.7.1.3 測試程序:
package com.ghw.fitst;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.log4j.Logger;
import org.junit.Test;
import com.ghw.po.User;
public class Mybatis_Test {
static Logger logger = Logger.getLogger(Mybatis_Test.class);
@Test
public void findUserByIdTest() throws IOException {
// 讀取mybatis配置文件
String resource = "SqlMapConfig.xml";
// 得到配置文件流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 創(chuàng)建會話工廠,傳入mybatis配置文件流
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 通過工廠得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通過SqlSession操作數(shù)據(jù)庫
// 第一個參數(shù):映射文件中statement的id,等于=namespace+"."+statement的id
// 第二個參數(shù):指定和映射文件中所匹配的parameterType類型的參數(shù)
// sqlSession.selectOne結(jié)果 是與映射文件中所匹配的resultType類型的對象
// selectOne查詢出一條記錄
User user = sqlSession.selectOne("test.findUserById", 1);
// 輸出查詢到的結(jié)果
logger.info(user);
}
@Test
public void findUserByNameTest() throws IOException {
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> list = sqlSession.selectList("test.findUserByusername", "小明");
logger.info(list);
}
}
1.3.7.1.4 #{}和${}
#{}
表示一個占位符號,通過#{}
可以實現(xiàn)preparedStatement
向占位符中設置值,自動進行java
類型和jdbc
類型轉(zhuǎn)換,#{}
可以有效防止sql
注入。#{}
可以接收簡單類型值或pojo
屬性值。
如果parameterType
傳輸單個簡單類型值,#{}
括號中可以是value
或其它名稱。
${}
表示拼接sql
串,通過${}
可以將parameterType
傳入的內(nèi)容拼接在sql
中且不進行jdbc
類型轉(zhuǎn)換, ${}
可以接收簡單類型值或pojo
屬性值,如果parameterType
傳輸單個簡單類型值,${}
括號中只能是value
。可能引起sql注入
1.3.7.1.5 parameterType 和resultType
parameterType:指定輸入?yún)?shù)類型,mybatis 通過ognl 從輸入對象中獲取參數(shù)值拼接在sql中。
resultType:指定輸出結(jié)果類型,mybatis 將sql 查詢結(jié)果的一行記錄數(shù)據(jù)映射為resultType指定類型的對象。
1.3.7.1.6 selectOne 和selectList
selectOne 查詢一條記錄,如果使用selectOne 查詢多條記錄則拋出異常:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result(or null) to be returned by selectOne(), but found: 3
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultS
qlSession.java:70)
selectList 可以查詢一條或多條記錄。
1.3.7.2添加
1.3.7.2.1 映射文件:
在SqlMapConfig.xml 中添加:
<!-- 添加用戶 -->
<insert id="insertUser" parameterType="com.ghw.po.User">
insert into user(id,username,birthday,sex,address)
values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>
1.3.7.2.2 測試程序:
// 添加用戶
public void insertUser() {
User user1 = new User();
user1.setUsername("李書豪");
user1.setBirthday(new Date());
user1.setSex("男");
user1.setAddress("陜西渭南");
//在@before中已經(jīng)獲取了sqlSession
sqlSession.insert("test.insertUser", user1);
//提交事務
sqlSession.commit();
}
1.3.7.2.3 mysql 自增主鍵返回
通過修改sql 映射文件,可以將mysql 自增主鍵返回:
<insert id="insertUser" parameterType="com.ghw.po.User">
<selectKey keyProperty="id" order="AFTER" resultType="Integer">
select LAST_INSERT_ID()
</selectKey>
insert into user(username,birthday,sex,address)
values(#{username},#{birthday},#{sex},#{address})
</insert>
添加selectKey 實現(xiàn)將主鍵返回
keyProperty:返回的主鍵存儲在pojo中的哪個屬性
order:selectKey 的執(zhí)行順序,是相對與insert 語句來說,由于mysql 的自增原理執(zhí)行完insert 語句之后才將主鍵生成,所以這里selectKey 的執(zhí)行順序為after
resultType:返回的主鍵是什么類型
LAST_INSERT_ID():是mysql 的函數(shù),返回auto_increment 自增列新記錄id 值。
1.3.7.2.4 Mysql 使用uuid 實現(xiàn)主鍵
需要增加通過select uuid()得到uuid 值
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey resultType="java.lang.String" order="BEFORE"
keyProperty="id">
select uuid()
</selectKey>
insert into user(id,username,birthday,sex,address)
values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>
注意這里使用的order 是“BEFORE”
1.3.7.2.5 Oracle 使用序列生成主鍵
首先自定義一個序列且用于生成主鍵,selectKey 使用如下:
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey resultType="java.lang.Integer" order="BEFORE" keyProperty="id">
SELECT 自定義序列.NEXTVAL FROM DUAL
</selectKey>
insert into user(id,username,birthday,sex,address) values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>
注意這里使用的order
是BEFORE
1.3.7.3刪除
1.3.7.3.1 映射文件:
<!-- 刪除用戶 -->
<delete id="deleteUser" parameterType="int">
delete from user where id = #{id}
</delete>
1.3.7.3.2 測試程序:
// 刪除用戶
@Test
public void deleteUser() {
// 刪除編號32的用戶
sqlSession.delete("test.deleteUser", 32);
// 提交事務
sqlSession.commit();
logger.info("刪除成功");
}
1.3.7.4修改
1.3.7.4.1 映射文件
<!-- 修改用戶 -->
<!-- 修改用戶 -->
<update id="updateUser" parameterType="com.ghw.po.User">
update user set
username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
where id = #{id}
</update>
1.3.7.4.2 測試程序
// 修改用戶
@Test
public void updateUser() {
User user2 = new User();
user2.setId(26);
user2.setBirthday(new Date());
user2.setSex("男");
user2.setUsername("李書豪2");
user2.setAddress("西安郵電大學");
sqlSession.update("test.updateUser", user2);
sqlSession.commit();
logger.info("修改成功");
}
1.3.8 Mybatis 解決jdbc 編程的問題
- 數(shù)據(jù)庫鏈接頻繁建立與釋放鏈接,造成系統(tǒng)資源浪費。
解決:在SqlMapConfig.xml
中配置數(shù)據(jù)鏈接池,使用連接池管理數(shù)據(jù)庫鏈接。 - sql語句寫在java代碼中,修改的時候要修改源代碼,不利于后期維護升級。
解決:將sql語句配置在XXXXmapper.xml 文件中與java 代碼分離。 - 向sql 語句傳參數(shù)麻煩,因為sql 語句的where 條件不一定,可能多也可能少,占位符需要和參數(shù)一一對應。
解決:Mybatis
自動將java 對象映射至sql
語句,通過statement
中的parameterType
定義輸入?yún)?shù)的類型。 - 對結(jié)果集解析麻煩,sql 變化導致解析代碼變化,且解析前需要遍歷,如果能將數(shù)據(jù)庫記錄封裝成pojo 對象解析比較方便。
解決:Mybatis 自動將sql 執(zhí)行結(jié)果映射至java 對象,通過statement 中的resultType 定義輸出結(jié)果的類型。
1.3.9 與hibernate 不同
- Mybatis 和hibernate 不同,它不完全是一個ORM 框架,因為MyBatis 需要程序員自己編寫Sql 語句,不過mybatis 可以通過XML 或注解方式靈活配置要運行的sql 語句,并將java對象和sql 語句映射生成最終執(zhí)行的sql,最后將sql 執(zhí)行的結(jié)果再映射生成java 對象。
- Mybatis 學習門檻低,簡單易學,程序員直接編寫原生態(tài)sql,可嚴格控制sql 執(zhí)行性能,靈活度高,非常適合對關系數(shù)據(jù)模型要求不高的軟件開發(fā),例如互聯(lián)網(wǎng)軟件、企業(yè)運營類軟件等,因為這類軟件需求變化頻繁,一但需求變化要求成果輸出迅速。但是靈活的前提是mybatis 無法做到數(shù)據(jù)庫無關性,如果需要實現(xiàn)支持多種數(shù)據(jù)庫的軟件則需要自定義多套sql映射文件,工作量大。
- Hibernate 對象/關系映射能力強,數(shù)據(jù)庫無關性好,對于關系模型要求高的軟件(例如需求固定的定制化軟件)如果用hibernate 開發(fā)可以節(jié)省很多代碼,提高效率。但是Hibernate的學習門檻高,要精通門檻更高,而且怎么設計O/R 映射,在性能和對象模型之間如何權衡,以及怎樣用好Hibernate 需要具有很強的經(jīng)驗和能力才行。
- 總之,按照用戶的需求在有限的資源環(huán)境下只要能做出維護性、擴展性良好的軟件架構都是好架構,所以框架只有適合才是最好。
2 Dao 開發(fā)方法
使用Mybatis 開發(fā)Dao,通常有兩個方法,即原始Dao開發(fā)方法和Mapper接口開發(fā)方法。
2.1 需求
將下邊的功能實現(xiàn)Dao:
- 根據(jù)用戶id 查詢一個用戶信息
- 根據(jù)用戶名稱模糊查詢用戶信息列表
- 添加用戶信息
2.2 SqlSession 的使用范圍
SqlSession 中封裝了對數(shù)據(jù)庫的操作,如:查詢、插入、更新、刪除等。通過SqlSessionFactory 創(chuàng)建SqlSession,而SqlSessionFactory 是通過SqlSessionFactoryBuilder進行創(chuàng)建。
2.2.1 SqlSessionFactoryBuilder
SqlSessionFactoryBuilder 用于創(chuàng)建SqlSessionFacoty,SqlSessionFacoty 一旦創(chuàng)建完成就不需要SqlSessionFactoryBuilder 了,因為SqlSession 是通過SqlSessionFactory 生產(chǎn),所以可以將SqlSessionFactoryBuilder 當成一個工具類使用,最佳使用范圍是方法范圍即方法體內(nèi)局部變量。
2.2.2 SqlSessionFactory
SqlSessionFactory是一個接口,接口中定義了openSession 的不同重載方法SqlSessionFactory的最佳使用范圍是整個應用運行期間,一旦創(chuàng)建后可以重復使用,通常以單例模式管理SqlSessionFactory。
2.2.3 SqlSession
SqlSession 是一個面向用戶的接口, sqlSession 中定義了數(shù)據(jù)庫操作, 默認使用
DefaultSqlSession 實現(xiàn)類。
執(zhí)行過程如下:
- 加載數(shù)據(jù)源等配置信息
Environment environment = configuration.getEnvironment(); - 創(chuàng)建數(shù)據(jù)庫鏈接
- 創(chuàng)建事務對象
- 創(chuàng)建Executor,SqlSession 所有操作都是通過Executor 完成,mybatis 源碼如下:
if (ExecutorType.BATCH == executorType) {
executor = newBatchExecutor(this, transaction);
} elseif (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor, autoCommit);
}
- SqlSession 的實現(xiàn)類即DefaultSqlSession,此對象中對操作數(shù)據(jù)庫實質(zhì)上用的是Executor
結(jié)論:
每個線程都應該有它自己的SqlSession 實例。SqlSession 的實例不能共享使用,它也是
線程不安全的。因此最佳的范圍是請求或方法范圍。絕對不能將SqlSession 實例的引用放在一個類的靜態(tài)字段或?qū)嵗侄沃小?/strong>打開一個SqlSession;使用完畢就要關閉它。通常把這個關閉操作放到finally 塊中以確保每次都能執(zhí)行關閉。如下:
SqlSession session = sqlSessionFactory.openSession();
try {
// do work
} finally {
session.close();
}
2.3 原始Dao 開發(fā)方式
原始Dao 開發(fā)方法需要程序員編寫Dao 接口和Dao 實現(xiàn)類。
2.3.1 映射文件
<?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="test">
<!-- 根據(jù)id獲取用戶信息 -->
<select id="findUserById" parameterType="int"
resultType="com.ghw.po.User">
select * from user where id = #{id}
</select>
<!-- 自定義條件查詢用戶列表 -->
<select id="findUserByusername" parameterType="String"
resultType="com.ghw.po.User">
select * from user where username like '%${value}%'
</select>
<!-- 添加用戶 -->
<insert id="insertUser" parameterType="com.ghw.po.User">
<selectKey keyProperty="id" order="AFTER"
resultType="Integer">
select LAST_INSERT_ID()
</selectKey>
insert into user(username,birthday,sex,address)
values(#{username},#{birthday},#{sex},#{address})
</insert>
<!-- 刪除用戶 -->
<delete id="deleteUser" parameterType="int">
delete from user where id
= #{id}
</delete>
<!-- 修改用戶 -->
<update id="updateUser" parameterType="com.ghw.po.User">
update user set
username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
where id = #{id}
</update>
</mapper>
2.3.2 Dao 接口
package com.ghw.dao;
import com.ghw.po.User;
public interface UserDao {
// 根據(jù)id查詢用戶
public User getUserById(int id) throws Exception;
// 添加用戶
public void insertUser(User user) throws Exception;
// 刪除用戶
public void deleteUser(int id) throws Exception;
// 修改用戶
public void updateUser(User user) throws Exception;
}
Dao實現(xiàn)類
package com.ghw.dao;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import com.ghw.po.User;
public class UserDaoImp implements UserDao {
private SqlSessionFactory sqlSessionFactory;
public UserDaoImp(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
// 根據(jù)id查詢用戶
public User getUserById(int id) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne("test.findUserById", id);
return user;
}
// 添加用戶
public void insertUser(User user) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.insert("test.insertUser", user);
sqlSession.commit();
}
// 刪除用戶
public void deleteUser(int id) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.delete("test.deleteUser", id);
sqlSession.commit();
}
// 修改用戶
public void updateUser(User user) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.update("test.updateUser", user);
sqlSession.commit();
}
}
Dao測試類
package com.ghw.dao;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import com.ghw.po.User;
public class UserDaoImpTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
// 讀取mybatis配置文件
String resource = "SqlMapConfig.xml";
// 得到配置文件流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 創(chuàng)建會話工廠,傳入mybatis配置文件流
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 通過工廠得到SqlSession
}
// 根據(jù)id查詢用戶測試方法
@Test
public void testGetUserById() throws Exception {
UserDao userDao = new UserDaoImp(sqlSessionFactory);
User user = userDao.getUserById(26);
}
// 添加用戶測試方法
@Test
public void testinsertUser() throws Exception {
UserDao userDao = new UserDaoImp(sqlSessionFactory);
User user1 = new User();
user1.setUsername("李書豪");
user1.setBirthday(new Date());
user1.setSex("男");
user1.setAddress("陜西渭南");
userDao.insertUser(user1);
}
// 刪除用戶測試方法
@Test
public void testdeleteUser() throws Exception {
UserDao userDao = new UserDaoImp(sqlSessionFactory);
userDao.deleteUser(29);
}
// 修改用戶測試方法
@Test
public void testupdateUser() throws Exception {
UserDao userDao = new UserDaoImp(sqlSessionFactory);
User user2 = new User();
user2.setId(26);
user2.setBirthday(new Date());
user2.setSex("女");
user2.setUsername("李書豪1");
user2.setAddress("西安郵電大學1");
userDao.updateUser(user2);
}
}
2.3.3 問題
原始Dao 開發(fā)中存在以下問題:
- Dao 方法體存在重復代碼:通過SqlSessionFactory 創(chuàng)建SqlSession,調(diào)用SqlSession 的數(shù)據(jù)庫操作方法
- 調(diào)用sqlSession 的數(shù)據(jù)庫操作方法需要指定statement 的id,這里存在硬編碼,不
得于開發(fā)維護。
2.4 Mapper 動態(tài)代理方式
2.4.1 實現(xiàn)原理
Mapper 接口開發(fā)方法只需要程序員編寫Mapper 接口(相當于Dao 接口),由Mybatis框架根據(jù)接口定義創(chuàng)建接口的動態(tài)代理對象,代理對象的方法體同上邊Dao 接口實現(xiàn)類方法。
Mapper 接口開發(fā)需要遵循以下規(guī)范:
- Mapper.xml 文件中的namespace 與mapper 接口的類路徑相同。
- Mapper 接口方法名和Mapper.xml 中定義的每個statement 的id 相同
- Mapper 接口方法的輸入?yún)?shù)類型和mapper.xml 中定義的每個sql 的parameterType 的類型相同
- Mapper 接口方法的輸出參數(shù)類型和mapper.xml 中定義的每個sql 的resultType 的類型相同
2.4.2 Mapper.xml(映射文件)
定義mapper 映射文件UserMapper.xml(內(nèi)容同Users.xml),需要修改namespace 的值為UserMapper 接口路徑。將UserMapper.xml 放在classpath 下mapper 目錄下。
<?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="test">
<!-- 根據(jù)id獲取用戶信息 -->
<select id="findUserById" parameterType="int"
resultType="com.ghw.po.User">
select * from user where id = #{id}
</select>
<!-- 自定義條件查詢用戶列表 -->
<select id="findUserByusername" parameterType="String"
resultType="com.ghw.po.User">
select * from user where username like '%${value}%'
</select>
<!-- 添加用戶 -->
<insert id="insertUser" parameterType="com.ghw.po.User">
<selectKey keyProperty="id" order="AFTER"
resultType="Integer">
select LAST_INSERT_ID()
</selectKey>
insert into user(username,birthday,sex,address)
values(#{username},#{birthday},#{sex},#{address})
</insert>
<!-- 刪除用戶 -->
<delete id="deleteUser" parameterType="int">
delete from user where id
= #{id}
</delete>
<!-- 修改用戶 -->
<update id="updateUser" parameterType="com.ghw.po.User">
update user set
username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
where id = #{id}
</update>
</mapper>
2.4.3 Mapper.java(接口文件)
package com.ghw.mapper;
import com.ghw.po.User;
public interface UserMapper {
// 根據(jù)id查詢用戶
public User findUserById(int id) throws Exception;
// 自定義條件查詢用戶列表
public User findUserByusername(String name) throws Exception;
// 添加用戶
public void insertUser(User user) throws Exception;
// 刪除用戶
public void deleteUser(int id) throws Exception;
// 修改用戶
public void updateUser(User user) throws Exception;
}
接口定義有如下特點:
- Mapper 接口方法名和Mapper.xml 中定義的statement 的id 相同
- Mapper 接口方法的輸入?yún)?shù)類型和mapper.xml 中定義的statement 的parameterType 的類型相同
- Mapper 接口方法的輸出參數(shù)類型和mapper.xml 中定義的statement 的resultType 的類型相同
2.4.4 加載UserMapper.xml 文件
修改SqlMapConfig.xml 文件:
<mappers>
<!-- 加載mapper映射文件 -->
<mapper resource="mapper/UserMapper.xml"></mapper>
</mappers>
2.4.5 測試
package com.ghw.mapper;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import com.ghw.po.User;
public class UserMapperTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
// 讀取mybatis配置文件
String resource = "SqlMapConfig.xml";
// 得到配置文件流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 創(chuàng)建會話工廠,傳入mybatis配置文件流
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 通過工廠得到SqlSession
}
// mapper根據(jù)id查詢用戶測試方法
@Test
public void testfindUserById() throws Exception {
// 獲取sqlsession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 獲取mapper接口的代理對象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 調(diào)用代理對象的方法
User user = userMapper.findUserById(26);
// 輸出查詢到的內(nèi)容
System.out.println(user);
// 關閉sqlsession
sqlSession.close();
}
// mapper自定義條件查詢用戶列表
@Test
public void testfindUserByusername() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 通過mapper接口查詢用戶列表
List<User> list = userMapper.findUserByusername("小明");
System.out.println(list);
// 關閉session
sqlSession.close();
}
// mapper添加用戶
@Test
public void testinsertUser() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = new User();
user1.setUsername("李書豪");
user1.setBirthday(new Date());
user1.setSex("男");
user1.setAddress("陜西渭南");
// 通過mapper接口添加用戶
userMapper.insertUser(user1);
// 提交
sqlSession.commit();
// 關閉session
sqlSession.close();
}
// mapper刪除用戶
@Test
public void testdeleteUser() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 通過mapper接口刪除
userMapper.deleteUser(30);
// 提交
sqlSession.commit();
// 關閉session
sqlSession.close();
}
// mapper修改用戶
@Test
public void testupdateUser() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user2 = new User();
user2.setId(28);
user2.setBirthday(new Date());
user2.setSex("女");
user2.setUsername("李書豪1");
user2.setAddress("西安郵電大學1");
// 通過mapper接口修改用戶
userMapper.updateUser(user2);
// 提交
sqlSession.commit();
// 關閉session
sqlSession.close();
}
}
2.4.6 總結(jié)
-
selectOne
和selectList
動態(tài)代理對象調(diào)用sqlSession.selectOne()
和sqlSession.selectList()
是根據(jù)mapper 接口方法的返回值決定,如果返回List
則調(diào)用selectList()
方法,如果返回單個對象則調(diào)用selectOne()
方法。 -
namespace
mybatis
官方推薦使用mapper
代理方法開發(fā)mapper
接口,程序員不用編寫mapper
接口實現(xiàn)類,使用mapper
代理方法時,輸入?yún)?shù)可以使用pojo
包裝對象或map
對象,保證dao
的通用性。
3 SqlMapConfig.xml 配置文件
3.1 配置內(nèi)容
SqlMapConfig.xml 中配置的內(nèi)容和順序如下:
properties(屬性)
settings(全局配置參數(shù))
typeAliases(類型別名)
typeHandlers(類型處理器)
objectFactory(對象工廠)
plugins(插件)
environments(環(huán)境集合屬性對象)
environment(環(huán)境子屬性對象)
transactionManager(事務管理)
dataSource(數(shù)據(jù)源)
mappers(映射器)
3.2 properties(屬性)
SqlMapConfig.xml
可以引用java
屬性文件中的配置信息如下:
在classpath
(類路徑,Source Folder文件夾下就是類路徑)下定義db.properties
文件,內(nèi)容如下:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis001?characterEncoding=utf-8
jdbc.username=root
jdbc.password=admin
SqlMapConfig.xml
引用如下:
<properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<!-- 使用jdbc事務管理 -->
<transactionManager type="JDBC" />
<!-- 數(shù)據(jù)庫連接池 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
注意: MyBatis 將按照下面的順序來加載屬性:
- 在
properties
元素體內(nèi)定義的屬性首先被讀取。 - 然后會讀取
properties
元素中resource
或url
加載的屬性,它會覆蓋已讀取的同名屬性。 - 最后讀取
parameterType
傳遞的屬性,它會覆蓋已讀取的同名屬性。 - 因此,
通過parameterType
傳遞的屬性具有最高優(yōu)先級,resource
或url
加載的屬性次之,最低優(yōu)先級的是properties
元素體內(nèi)定義的屬性。
3.3 settings(配置)
mybatis
全局配置參數(shù),全局參數(shù)將會影響mybatis
的運行行為。
3.4 typeAliases(類型別名)
3.4.1 mybatis 支持別名:
別名 | 映射的類型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
3.4.2 自定義別名:
在SqlMapConfig.xml
中配置:
<typeAliases>
<!-- 單個別名定義 -->
<typeAlias alias="user" type="com.ghw.po.User" />
<!-- 批量別名定義,掃描整個包下的類,別名為類名(首字母大寫或小寫都可以) -->
<package name="com.ghw.po" />
<package name="其它包" />
</typeAliases>
3.5 typeHandlers
(類型處理器)
類型處理器用于java
類型和jdbc
類型映射,如下:
<select id="findUserById" parameterType="int" resultType="user">
select * from user where id = #{id}
</select>
mybatis 自帶的類型處理器基本上滿足日常需求,不需要單獨定義。
mybatis 支持類型處理器:
類型處理器 | Java類型 | JDBC類型 |
---|---|---|
BooleanTypeHandler | Boolean,boolean | 任何兼容的布爾值 |
ByteTypeHandler | Byte,byte | 任何兼容的數(shù)字或字節(jié)類型 |
ShortTypeHandler | Short,short | 任何兼容的數(shù)字或短整型 |
IntegerTypeHandler | Integer,int | 任何兼容的數(shù)字和整型 |
LongTypeHandler | Long,long | 任何兼容的數(shù)字或長整型 |
FloatTypeHandler | Float,float | 任何兼容的數(shù)字或單精度浮點型 |
DoubleTypeHandler | Double,double | 任何兼容的數(shù)字或雙精度浮點型 |
BigDecimalTypeHandler | BigDecimal | 任何兼容的數(shù)字或十進制小數(shù)類型 |
StringTypeHandler | String | CHAR和VARCHAR類型 |
ClobTypeHandler | String | CLOB和LONGVARCHAR類型 |
NStringTypeHandler | String | NVARCHAR和NCHAR類型 |
NClobTypeHandler | String | NCLOB類型 |
ByteArrayTypeHandler | byte[] | 任何兼容的字節(jié)流類型 |
BlobTypeHandler | byte[] | BLOB和LONGVARBINARY類型 |
DateTypeHandler | Date(java.util) | TIMESTAMP類型 |
DateOnlyTypeHandler | Date(java.util) | DATE類型 |
TimeOnlyTypeHandler | Date(java.util) | TIME類型 |
SqlTimestampTypeHandler | Timestamp(java.sql) | TIMESTAMP類型 |
SqlDateTypeHandler | Date(java.sql) | DATE類型 |
SqlTimeTypeHandler | Time(java.sql) | TIME類型 |
ObjectTypeHandler | 任意 | 其他或未指定類型 |
EnumTypeHandler | Enumeration類型 | VARCHAR-任何兼容的字符串類型,作為代碼存儲(而不是索引)。 |
3.6 mappers(映射器)
Mapper 配置的幾種方法:
3.6.1 <mapper resource=" " />
使用相對于類路徑的資源
如:<mapper resource="sqlmap/User.xml" />
3.6.2 <mapper url=" " />
使用完全限定路徑
如:<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />
3.6.3 <mapper class=" " />
使用mapper 接口類路徑
如:<mapper class="cn.itcast.mybatis.mapper.UserMapper"/>
注意:此種方法要求mapper 接口名稱和mapper 映射文件名稱相同,且放在同一個目錄中。
3.6.4 <package name=""/>
注冊指定包下的所有mapper 接口
如:<package name="cn.itcast.mybatis.mapper"/>
注意:此種方法要求mapper 接口名稱和mapper 映射文件名稱相同,且放在同一個目錄中。
4 Mapper.xml 映射文件
Mapper.xml 映射文件中定義了操作數(shù)據(jù)庫的sql,每個sql 是一個statement,映射文件是mybatis 的核心。
4.1 parameterType(輸入類型)
4.1.1 #{}與${}
#{}
實現(xiàn)的是向prepareStatement 中的預處理語句中設置參數(shù)值,sql 語句中#{}表示一個占位
符即?。
<!-- 根據(jù)id獲取用戶信息 -->
<select id="findUserById" parameterType="int"
resultType="com.ghw.po.User">
select * from user where id = #{id}
</select>
使用占位符#{}可以有效防止sql 注入,在使用時不需要關心參數(shù)值的類型,mybatis 會自動
進行java 類型和jdbc 類型的轉(zhuǎn)換。#{}可以接收簡單類型值或pojo 屬性值,如果parameterType
傳輸單個簡單類型值,#{}括號中可以是value 或其它名稱。
${}和#{}不同,通過${}可以將parameterType 傳入的內(nèi)容拼接在sql 中且不進行jdbc 類型轉(zhuǎn)
換, ${}可以接收簡單類型值或pojo 屬性值,如果parameterType 傳輸單個簡單類型值,${}
括號中只能是value。使用${}不能防止sql 注入,但是有時用${}會非常方便,如下的例子:
<!-- 自定義條件查詢用戶列表 -->
<select id="findUserByusername" parameterType="String"
resultType="com.ghw.po.User">
select * from user where username like '%${value}%'
</select>
如果本例子使用#{}則傳入的字符串中必須有%號,而%是人為拼接在參數(shù)中,顯然有點麻煩,如果采用${}在sql 中拼接為%的方式則在調(diào)用mapper 接口傳遞參數(shù)就方便很多。
// 如果使用占位符號則必須人為在傳參數(shù)中加%
List<User> list = userMapper.selectUserByName("%管理員%");
// 如果使用${}原始符號則不用人為在參數(shù)中加%
List<User> list = userMapper.selectUserByName("管理員");
再比如order by 排序,如果將列名通過參數(shù)傳入sql,根據(jù)傳的列名進行排序,應該寫為:
ORDER BY ${columnName}
如果使用#{}將無法實現(xiàn)此功能。
4.1.2 傳遞簡單類型
參考上邊的例子。
4.1.3 傳遞pojo 對象
Mybatis 使用ognl 表達式解析對象字段的值,如下例子:
<!-- 傳遞pojo對象綜合查詢用戶信息 -->
<select id="findUserByUser" parameterType="user"
resultType="user">
select * from user where id=#{id} and username like '%${username}%'
</select>
上邊紅色標注的是user 對象中的字段名稱。
測試:
public void testFindUserByUser() throws Exception {
// 獲取session
SqlSession session = sqlSessionFactory.openSession();
// 獲限mapper接口實例
UserMapper userMapper = session.getMapper(UserMapper.class);
// 構造查詢條件user對象
User user = new User();
user.setId(1);
user.setUsername("管理員");
// 傳遞user對象查詢用戶列表
List<User> list = userMapper.findUserByUser(user);
// 關閉session
session.close();
}
異常測試:
Sql
中字段名輸入錯誤后測試,username 輸入dusername 測試結(jié)果報錯:
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'dusername' in 'where clause'
### The error may exist in mapper/UserMapper.xml
### The error may involve com.ghw.mapper.UserMapper.findUserByusername-Inline
### The error occurred while setting parameters
4.1.4 傳遞pojo 包裝對象
開發(fā)中通過pojo 傳遞查詢條件,查詢條件是綜合的查詢條件,不僅包括用戶查詢條件
還包括其它的查詢條件(比如將用戶購買商品信息也作為查詢條件),這時可以使用包裝對象傳遞輸入?yún)?shù)。
4.1.4.1定義包裝對象
定義包裝對象將查詢條件(pojo)以類組合的方式包裝起來。
public class QueryVo {
private User user;
//自定義用戶擴展類
private UserCustom userCustom;
4.1.4.2mapper.xml 映射文件
<!-- 查詢用戶列表 根據(jù)用戶名稱和用戶性別查詢用戶列表 -->
<select id="findUserList" parameterType="queryVo"
resultType="user">
select * from user where username = #{user.username} and sex = #{user.sex}
</select>
說明:mybatis 底層通過ognl 從pojo 中獲取屬性值:#{user.username},user 即是傳入的包裝對象的屬性。queryVo 是別名,即上邊定義的包裝對象類型。
說明:mybatis 底層通過 ognl 從 pojo 中獲取屬性值:#{user.username},user 即是傳入的包 裝對象的屬性。queryVo 是別名,即上邊定義的包裝對象類型。
4.1.5 傳遞hashmap
Sql 映射文件定義如下:
<!-- 傳遞hashmap綜合查詢用戶信息 -->
<select id="findUserByHashmap" parameterType="hashmap"
resultType="user"> select * from user where id=#{id} and username like
'%${username}%'
</select>
上面的id和username是hashmap的key
測試:
public void testFindUserByHashmap() throws Exception {
// 獲取session
SqlSession session = sqlSessionFactory.openSession();
// 獲限mapper接口實例
UserMapper userMapper = session.getMapper(UserMapper.class);
// 構造查詢條件Hashmap對象
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("id", 1);
map.put("username", "管理員");
// 傳遞Hashmap對象查詢用戶列表 List<User>list = userMapper.findUserByHashmap(map);
// //關閉session session.close();
}
異常測試: 傳遞的 map 中的 key 和 sql 中解析的 key 不一致。 測試結(jié)果沒有報錯,只是通過 key 獲取值為空。
4.2 resultType(輸出類型)
4.2.1輸出簡單類型
參考 getnow 輸出日期類型,看下邊的例子輸出整型:
Mapper.xml 文件
<!-- 獲取用戶列表總數(shù) -->
<select id="findUserCount" parameterType="user" resultType="int">
select count(1) from user
</select>
Mapper 接口
public int findUserCount(User user) throws Exception;
調(diào)用:
public void testFindUserCount() throws Exception {
// 獲取session
SqlSession session = sqlSessionFactory.openSession();
// 獲取mapper接口實例
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = new User();
user.setUsername("管理員");
// 傳遞Hashmap對象查詢用戶列表
int count = userMapper.findUserCount(user);
// 關閉session session.close();
}
總結(jié): 輸出簡單類型必須查詢出來的結(jié)果集有一條記錄,最終將第一個字段的值轉(zhuǎn)換為輸出類型。 使用 session 的 selectOne 可查詢單條記錄。
4.2.2輸出 pojo 對象
參考 findUserById 的定義:
Mapper.xml
<!-- 根據(jù)id查詢用戶信息 -->
<select id="findUserById" parameterType="int" resultType="user"> select
* from user where id = #{id}
</select>
Mapper 接口:
public User findUserById(int id) throws Exception;
測試:
public void testFindUserById() throws Exception {
// 獲取session
SqlSession session = sqlSessionFactory.openSession();
// 獲限mapper接口實例
UserMapper userMapper = session.getMapper(UserMapper.class);
// 通過mapper接口調(diào)用statement
User user = userMapper.findUserById(1);
System.out.println(user);
// 關閉session
session.close();
}
使用 session 調(diào)用 selectOne 查詢單條記錄。
4.2.3輸出 pojo 列表
參考 selectUserByName 的定義:
Mapper.xml
<!-- 根據(jù)名稱模糊查詢用戶信息 -->
<select id="findUserByUsername" parameterType="string"
resultType="user"> select * from user where username like '%${value}%'
</select>
Mapper 接口:
public List<User> findUserByUsername(String username) throws Exception;
測試:
public void testFindUserByUsername() throws Exception {
// 獲取session
SqlSession session = sqlSessionFactory.openSession();
// 獲限mapper接口實例
UserMapper userMapper = session.getMapper(UserMapper.class);
// 如果使用占位符號則必須人為在傳參數(shù)中加%
// List<User> list = userMapper.selectUserByName("%管理員%");
// 如果使用${}原始符號則不用人為在參數(shù)中加%
List<User> list = userMapper.findUserByUsername("管理員");
// 關閉session
session.close();
}
使用 session 的 selectList 方法獲取 pojo 列表。
4.2.4 resultType 總結(jié):
輸出 pojo 對象和輸出 pojo 列表在 sql 中定義的 resultType 是一樣的。 返回單個pojo 對象要保證 sql 查詢出來的結(jié)果集為單條,內(nèi)部使用 session.selectOne 方法調(diào)用,mapper 接口使用 pojo 對象作為方法返回值。
返回 pojo 列表表示查詢出來的結(jié)果集可能為多條,內(nèi)部使用 session.selectList 方法,mapper 接口使用 List<pojo>對象作為方法返回值。
4.2.5 輸出 hashmap
輸出 pojo 對象可以改用 hashmap 輸出類型,將輸出的字段名稱作為 map 的 key,value 為字 段值。
4.3 resultMap
resultType 可以指定 pojo 將查詢結(jié)果映射為 pojo,但需要 pojo 的屬性名和 sql 查詢的列 名一致方可映射成功。 如果 sql 查詢字段名和 pojo 的屬性名不一致,可以通過 resultMap 將字段名和屬性名作 一個對應關系 ,resultMap 實質(zhì)上還需要將查詢結(jié)果映射到 pojo 對象中。 resultMap 可以實現(xiàn)將查詢結(jié)果映射為復雜類型的 pojo,比如在查詢結(jié)果映射對象中包 括 pojo 和 list 實現(xiàn)一對一查詢和一對多查詢。
4.3.1 Mapper.xml 定義
<!-- 查詢用戶列表 根據(jù)用戶名稱和用戶性別查詢用戶列表 -->
<select id="findUserListResultMap" parameterType="queryVo"
resultMap="userListResultMap">
select id id_,username username_,birthday birthday_ from user
<!-- where自動將第一個and去掉 -->
<where>
<!-- refid:指定sql片段的id,如果要引用其他命名空間的sql片段,需要前邊加namaspcae -->
<include refid="query_user_where"></include>
</where>
</select>
使用 resultMap 指定上邊定義的 personmap。
4.3.2定義 resultMap
由于上邊的 mapper.xml 中 sql 查詢列和 Users.java 類屬性不一致,需要定義 resultMap: userListResultMap 將 sql 查詢列和 Users.java 類屬性對應起來
<!-- 定義resultMap,將用戶查詢的字段和user這個pojo的屬性作一個對應關系 -->
<!-- type:最終映射的java對象 id:resultMap的唯一標識 -->
<resultMap type="user" id="userListResultMap">
<!-- id標簽:將查詢結(jié)果集的唯一標識列(主鍵或唯一標識)
column:sql查詢字段名(列名)
property:pojo屬性名 -->
<id column="id_" property="id" />
<result column="username_" property="username" />
<result column="birthday_" property="birthday" />
</resultMap>
<id/>:此屬性表示查詢結(jié)果集的唯一標識,非常重要。如果是多個字段為復合唯一約束則 定義多個<id/>。
Property:表示 person 類的屬性。
Column:表示 sql 查詢出來的字段名。
Column 和 property 放在一塊兒表示將 sql 查詢出來的字段映射到指定的 pojo 類屬性上。
<result/>:普通結(jié)果,即 pojo 的屬性
4.3.3 Mapper 接口定義
public List<User> findUserListResultMap() throws Exception;
4.4 動態(tài) sql(重點)
通過 mybatis 提供的各種標簽方法實現(xiàn)動態(tài)拼接 sql。