在知道Mybatis(原名ibatis)怎麼用以前,對於在代碼中鏈接數據庫,我都是用JDBC鏈接的,例如這樣:html
Connection connection = DriverManager.getConnection(jdbcUrl, user, password);
String sqlCommend = "select goods.id,goods.name,sum(`order`.goods_num*goods_price) as gmv from `order` \n" +
"join goods\n" +
"on goods.id = `order`.goods_id\n" +
"group by goods_id \n" +
"order by gmv desc\n" +
"\n";
try (PreparedStatement pS = databaseConnection.prepareStatement(sqlCommend)) {
ResultSet resultSet = pS.executeQuery();
return getGoodsAndGmv(resultSet);
}
複製代碼
這樣看起來也沒多麻煩,可是誰也不想本身的函數中出現這麼一段不堪的語句。因此,Mybatis爲咱們提供了更加方便的執行數據庫操做的方法。
Mybatis自己也是一種ORM(Object Relationship Mapping)框架,既對象關係映射,說白了就是實現數據庫到Java對象的一個映射,就是咱們與數據庫打交道的一箇中間層。
Mybatis的官方文檔寫的很是詳細,你碰到的問題基本上均可以經過官方文檔解決。java
跟着官方文檔一步步走,首先須要從外部引入Mybatis的jar包,使用Maven的話則須要引入Maven配置,接下來就是配置資源文件了。首先明白,在Java中把非代碼的內容都稱爲資源,包括圖片、視頻、數據庫等,資源目錄與代碼目錄結構相似。git
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
複製代碼
注意:github
<!--數據庫的驅動類型-->
<property name="driver" value="org.h2.Driver"/>
<!--數據庫的鏈接串-->
<property name="url" value="jdbc:h2:file:H:/githubitem/SinaCrawler/sina-crawler/SinaCrawler"/>
<!--用戶名-->
<property name="username" value="root"/>
<!--密碼-->
<property name="password" value="password"/>
複製代碼
以及sql
<mappers>
<mapper resource="XXX/XXX/XXX/XXX.xml"/>
<mapper resource="XXX/XXX/XXX/XXX$XXX"/>
</mappers>
複製代碼
是須要根據本身的實際狀況修改的。這裏用的是本身的一個H2數據庫爲例子,
數據庫中的內容爲:數據庫
用戶表:
+----+----------+------+----------+
| ID | NAME | TEL | ADDRESS |
+----+----------+------+----------+
| 1 | zhangsan | tel1 | beijing |
+----+----------+------+----------+
| 2 | lisi | tel2 | shanghai |
+----+----------+------+----------+
| 3 | wangwu | tel3 | shanghai |
+----+----------+------+----------+
| 4 | zhangsan | tel4 | shenzhen |
+----+----------+------+----------+
商品表:
+----+--------+-------+
| ID | NAME | PRICE |
+----+--------+-------+
| 1 | goods1 | 10 |
+----+--------+-------+
| 2 | goods2 | 20 |
+----+--------+-------+
| 3 | goods3 | 30 |
+----+--------+-------+
| 4 | goods4 | 40 |
+----+--------+-------+
| 5 | goods5 | 50 |
+----+--------+-------+
訂單表:
+------------+-----------------+------------------+---------------------+-------------------------------+
| ID(訂單ID) | USER_ID(用戶ID) | GOODS_ID(商品ID) | GOODS_NUM(商品數量) | GOODS_PRICE(下單時的商品單價) |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 1 | 1 | 1 | 5 | 10 |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 2 | 2 | 1 | 1 | 10 |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 3 | 2 | 1 | 2 | 10 |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 4 | 4 | 2 | 4 | 20 |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 5 | 4 | 2 | 100 | 20 |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 6 | 4 | 3 | 1 | 20 |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 7 | 5 | 4 | 1 | 20 |
+------------+-----------------+------------------+---------------------+-------------------------------+
| 8 | 5 | 6 | 1 | 60 |
+------------+-----------------+------------------+---------------------+-------------------------------+
複製代碼
接下來先用一個簡單的例子來說解整個過程:
獲取全部的用戶信息: 寫一個接口:apache
public interface UserMapper{
@Select("select * from user")
List<User> getUsers();
}
複製代碼
實現這個接口:編程
public static void main(String[] args) throws IOException {
String resource = "db/mybatis/config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//得到SqlSession實例
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
//生成代理類
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user:users
) {
System.out.println(user);
}
}
複製代碼
config.xml配置文件中添加這個接口的絕對路徑安全
<mappers>
<!-- $區份內部類的分隔符 -->
<mapper class="com.github.hcsp.sql.Sql$UserMapper"/>
</mappers>
複製代碼
點擊運行就能夠看到結果了:bash
User{id=1, name='zhangsan', tel='tel1', address='beijing'}
User{id=2, name='lisi', tel='tel2', address='shanghai'}
User{id=3, name='wangwu', tel='tel3', address='shanghai'}
User{id=4, name='zhangsan', tel='tel4', address='shenzhen'}
Process finished with exit code 0
複製代碼
問題來了,咱們並無實現這個接口,那麼結果是怎麼出來的呢?看到MapperRegistry中的getMapper:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
複製代碼
能夠看到,這裏使用了代理模式,Mybatis識別出了@Select註解,並生成代理類,在代理類中包含接口的實現方法。
再問一個問題:
能夠看到,在咱們在數據庫中查詢數據的結果是這樣的,Mybatis是怎麼把它轉換爲User類的呢?
其實這也是經過反射完成的,根據每一列的列名去查找User類中的成員變量,根據查到的行數生成對應個數的對象。
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
複製代碼
注意放置的位置:
# Global logging configuration
# 日誌等級爲DEBUG,標準輸出
log4j.rootLogger=DEBUG, stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
# 標準輸出 = 控制檯輸出源
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
# 標準佈局 = log4j模式化佈局
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# 轉化輸出的模式 = 優先級(佔5個字節)[線程名]
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
複製代碼
再次運行一下試試看:
DEBUG [main] - Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
DEBUG [main] - Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 2076287037.
DEBUG [main] - Setting autocommit to false on JDBC Connection [conn0: url=jdbc:h2:file:H:/github item/SinaCrawler/sina-crawler/SinaCrawler user=ROOT]
DEBUG [main] - ==> Preparing: select * from user
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Total: 4
User{id=1, name='zhangsan', tel='tel1', address='beijing'}
User{id=2, name='lisi', tel='tel2', address='shanghai'}
User{id=3, name='wangwu', tel='tel3', address='shanghai'}
User{id=4, name='zhangsan', tel='tel4', address='shenzhen'}
DEBUG [main] - Resetting autocommit to true on JDBC Connection [conn0: url=jdbc:h2:file:H:/github item/SinaCrawler/sina-crawler/SinaCrawler user=ROOT]
DEBUG [main] - Closing JDBC Connection [conn0: url=jdbc:h2:file:H:/github item/SinaCrawler/sina-crawler/SinaCrawler user=ROOT]
DEBUG [main] - Returned connection 2076287037 to pool.
Process finished with exit code 0
複製代碼
如今就能夠看到DEBUG等級及如下的日誌信息了。注意:
log4j.properties文件必須直接放在resources目錄下,不然系統會找不到該文件。 順便介紹一下日誌等級:
在log4j.jar/org/apache/log4j/Level類中能夠看到有關日誌等級的聲明:
TRACE對程序運行沒有影響,既不打印到控制檯也不輸出到文件,主要用以線上調試,若是須要查看TRACE等級的日誌,須要經過elog命令開啓TRACE,或者將程序日誌輸出級別降至TRACE。
默認狀況下,打印至終端,可是不歸檔到日誌文件。所以通常用於程序啓動時,查看日誌流水信息。
INFO等級的日誌信息都是一過性的,不會大量反覆輸出。該級別日誌默認狀況下會打印到終端和日誌文件。
代表程序處理中可能遇到的錯誤,以及非法數據。該警告是一過性的,可恢復不影響程序進行。
該錯誤發生後程序任然能夠運行,可是極有可能在某種不正常的狀況下運行。
錯誤直接致使程序沒法啓動,須要當即解決。
Mapper有兩種:
詳細介紹第二種Mapper。根據官網提示,新建Mapper.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="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
複製代碼
namespace-命名空間,本身隨便取個名字。 id -給本身的SQL語句取名。
resultType -返回值類型。
示例:
<mapper namespace="com.github.hcsp.sql.Sql">
<select id="selectUsers" resultType="Map">
select id,name,address,tel from User
</select>
複製代碼
在主函數中:
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
System.out.println(sqlSession.selectList("com.github.hcsp.sql.Sql.selectUsers"));
}
複製代碼
結果:
DEBUG [main] - <== Total: 4
[{ADDRESS=beijing, TEL=tel1, ID=1, NAME=zhangsan}, {ADDRESS=shanghai, TEL=tel2, ID=2, NAME=lisi}, {ADDRESS=shanghai, TEL=tel3, ID=3, NAME=wangwu}, {ADDRESS=shenzhen, TEL=tel4, ID=4, NAME=zhangsan}]
複製代碼
返回值類型:
前面的實例中,咱們的返回值類型爲Map,因此返回的是鍵值對。那麼,咱們咱們還能夠新建一個類來存放結果。 像這樣
public class User {
Integer id;
String name;
String address;
String tel;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' + ", address='" + address + '\'' + ", tel='" + tel + '\'' +
'}';
}
}
複製代碼
返回值類型爲User的全限定類名
<select id="selectUsers" resultType="com.github.hcsp.sql.User">
複製代碼
結果以下:
[User{id=1, name='zhangsan', address='beijing', tel='tel1'}, User{id=2, name='lisi', address='shanghai', tel='tel2'}, User{id=3, name='wangwu', address='shanghai', tel='tel3'}, User{id=4, name='zhangsan', address='shenzhen', tel='tel4'}]
複製代碼
能夠看到返回值就變成一個個的User了,讀寫參數都是遵照JavaBean約定使用getter()和setter()進行的。
注意:可使用類型別名,簡化resultType。假若有幾十個方法的返回值類型都是User類型,每次都去寫全限定類型實在是麻煩,並且包名不能動,不然返回值類型全都要動,因此設置類型別名頗有必要。在config.xml中添加如下內容:
<typeAliases>
<typeAlias alias="User" type="com.github.hcsp.sql.Sql.User"/>
</typeAliases>
複製代碼
注意添加順序,那麼返回值類型能夠直接寫:
<select id="selectUsers" resultType="User">
複製代碼
其實Map也是全限定類名java.lang.HashMap的簡寫。
傳入參數:
查找id爲1的用戶,能夠看到:
<E> List<E> selectList(String statement, Object parameter);
複製代碼
selectList還有一個帶parameter的多態方法,對於一個參數的SQL語句
select id,name,address,tel from User where id=#{id}
複製代碼
直接往裏面塞一個參數便可:
sqlSession.selectList("com.github.hcsp.sql.Sql.selectUsers",1)
複製代碼
若是有多個參數就放一個類進去:
User user = new User();
user.id=1;
user.name = "zhangsan";
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
System.out.println(sqlSession.selectList("com.github.hcsp.sql.Sql.selectUsers",user));
}
複製代碼
設置參數還可使用${},兩者區別在於${}只是簡單的替換,而#{}是防注入的替換。
演示一下SQL注入: 以這條語句爲例:
select id,name,address,tel from User where name='${name}' and id=${id}
複製代碼
我傳入這樣一個User:
User user = new User();
user.name = "'or 1=1--";
複製代碼
結果以下:
DEBUG [main] - ==> Preparing: select id,name,address,tel from User where name=''or 1=1--' and id= DEBUG [main] - ==> Parameters: DEBUG [main] - <== Total: 4 [User{id=1, name='zhangsan', tel='tel1', address='beijing'}, User{id=2, name='lisi', tel='tel2', address='shanghai'}, User{id=3, name='wangwu', tel='tel3', address='shanghai'}, User{id=4, name='zhangsan', tel='tel4', address='shenzhen'}] 複製代碼
能夠看到,我拿到了數據庫中的全部內容。因此,${}是不安全的傳參數的方法。
固然,除了每次都新建一個User對象這種耗費內存的方法以外,還能夠用Map:
Map<Object,Object> map = new HashMap<>();
map.put("name","zhangsan");
map.put("id",1);
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
System.out.println(sqlSession.selectList("com.github.hcsp.sql.Sql.selectUsers",map));
}
複製代碼
文字部分Mybatis官網上有說明,也有例子,本身寫兩個加深下理解。
<select id="selectUsers" resultType="User">
select id,name,address,tel from User where name='${name}'
<if test="id !=null">
and id=${id}
</if>
</select>
複製代碼
這裏注意,不要把where、and這種語句寫在if判斷外面,不然像
select * from user where
這種語句是不符合格式要求的,就會報錯。
<select id="selectUser" resultType="User">
select * from User
<choose>
<when test="name==zhangsan">
where name = 'zhangsan'
</when>
<otherwise>
where name = 'lisi'
</otherwise>
</choose>
</select>
複製代碼
第一次
map.put("name","lisi");
獲得的是select * from User where name = 'lisi'
,這沒問題,第二次map.put("name","zhangsan");
,獲得的仍是select * from User where name = 'lisi'
。這就蹊蹺了,其實緣由在於<when test="name==zhangsan">
,沒有把zhangsan用``包起來,Mybatis誤覺得zhangsan也是變量,等着你去傳值,而後將name傳入的`zhangsan`與null進行比較。全部不管後面參數怎麼傳,sql語句都不會按照你所想的邏輯去執行。同時這也證實了一點,name、zhangsan這種參數的初始值都是null。
where、trim、set
這個沒什麼好講的,直接看官網的例子一看就明白了。
foreach-實現批量更新SQL
先來個簡單的:
找出id在某個集合中的User
<select id="selectIdIn" resultType="User">
SELECT *
FROM User
WHERE id in
<!-- item/index-佔位符,collection-須要從哪一個集合中找出結果-->
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
複製代碼
map.put("list",Arrays.asList(1,2,3,5,6));
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
System.out.println(sqlSession.selectList("com.github.hcsp.sql.Sql.selectIdIn",map));
}
複製代碼
結果以下:
DEBUG [main] - ==> Preparing: SELECT * FROM User WHERE id in ( ? , ? , ? , ? , ? )
DEBUG [main] - ==> Parameters: 1(Integer), 2(Integer), 3(Integer), 5(Integer), 6(Integer)
DEBUG [main] - <== Total: 3
[User{id=1, name='zhangsan', tel='tel1', address='beijing'}, User{id=2, name='lisi', tel='tel2', address='shanghai'}, User{id=3, name='wangwu', tel='tel3', address='shanghai'}]
複製代碼
foreach幫咱們拼接了一個查找id在某個範圍內的sql語句。
批量向表中插入user:
SQL語句以下:
<insert id="batchInsertUsers" parameterType="map">
insert into User(id,name,tel,address)
values
<foreach item="user" collection="users" separator=",">
(#{user.id},#{user.name},#{user.tel},#{user.address})
</foreach>
</insert>
複製代碼
其實這樣看來Mybatis中的foreach與咱們平時寫的foreach語句很相似,user是迭代的項目,users是被迭代的集合,中間須要逗號鏈接。
DEBUG [main] - ==> Preparing: insert into User(id,name,tel,address) values (?,?,?,?) , (?,?,?,?)
DEBUG [main] - ==> Parameters: null, abcd(String), tel-abcd-1(String), addr-abcd(String), null, abcd(String), tel-abcd-2(String), addr-abcd(String)
複製代碼
能夠看到Mybatis幫咱們拼出了批量插入數據的語句。順便說一句parameterType不是必要的。
查詢訂單信息,只查詢用戶名、商品名齊全的訂單,即INNER JOIN方式
能夠看到:
public class Order {
private Integer id;
/** 訂單中的用戶 */
private User user;
/** 訂單中的商品 */
private Goods goods;
/** 訂單中的總成交金額 */
private BigDecimal totalPrice;
複製代碼
在order
表中嵌套了user
跟goods
,那麼Order的id能夠直接得到,如何把從order表中獲取到的結果,賦值給User、Goods類,這就須要使用Mybatis裏面的association嵌套了。
<select id="getInnerJoinOrders" resultMap="order">
select `order`.id as order_id,
user.name as user_name,
goods.name as goods_name,
`order`.goods_num as goods_num,
goods.price as goods_price,
`order`.goods_num * `order`.goods_price as total_price
from `order`
inner join goods on goods.id = `order`.goods_id
inner join user on user.id = `order`.user_id
</select>
<resultMap id="order" type="Order">
<result property="id" column="order_id"/>
<result property="totalPrice" column="total_price"/>
<association property="user" javaType="User">
<result property="name" column="user_name"/>
</association>
<association property="goods" javaType="Goods">
<result property="name" column="goods_name"/>
<result property="price" column="goods_price"/>
</association>
</resultMap>
複製代碼
能夠看到,對於這種狀況,咱們不能直接返回一個確切的resultType,而是返回一個結果映射resultMap,也就是在這個例子中,咱們的查詢結果order會被映射爲另外一個對象。