【JavaWeb_Part05】JDBC?弱爆了,看看輕量級的 Mybatis FreeStyle
開篇
上篇文章我們已經講了 Mybatis 的入門以及簡單的對單表進行增刪改查,那麼今天我們就來講一下使用 mybatis 開發dao的兩種方式以及 mysql 比較厲害的動態 sql。
利用 mybatis 開發 DAO
1. 原始的方式開發DAO
主要完成功能
1. 根據使用者 id 查詢使用者資訊
2. 根據使用者姓名查詢使用者列表
3. 插入使用者
- 新建一個介面 User1Dao.java 和一個實現類 User1DaoImpl.java,並在 User1Dao 中新增如下方法
User1Dao.java
public interface UserDao {
//根據使用者id 查詢使用者
User findUserById(Integer id);
//根據使用者名稱稱查詢使用者列表
List<User> findUserByName(String name);
//插入使用者
void insertUser(User user);
}
- 在實現類中實現如下方法
User1DaoImpl.java
public class User1DaoImpl implements UserDao{
private static SqlSessionFactory factory;
static {
String resource = "SqlMapConfig.xml" ;
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public User findUserById(Integer id) {
SqlSession session = factory.openSession();
User user= session.selectOne("test.findUserById", id);
return user;
}
@Override
public List<User> findUserByName(String name) {
SqlSession session = factory.openSession();
List<User> userList = session.selectList("test.findUserByName", name);
return userList;
}
@Override
public void insertUser(User user) {
SqlSession session = factory.openSession();
session.insert("test.insertUser", user);
session.commit();
}
}
注意
只有查詢方法不用提交事務,其他的,比如增加,刪除,更新等操作都要執行 session.close() 方法。
- 新建測試類 TestDao1.java
public class TestDao1 {
@Test
public void testFindUserById(){
System.out.println(new User1DaoImpl().findUserById(6));
}
@Test
public void testFindUserByName(){
List<User> userList = new User1DaoImpl().findUserByName("二");
for (User user :
userList) {
System.out.println(user);
}
}
@Test
public void testInsertUser(){
User user = new User();
user.setName("李二牛");
user.setAddress("湖北武漢");
user.setSex("1");
new User1DaoImpl().insertUser(user);
}
}
以上的結果就非常簡單了,在這裡我就不貼圖了,相信小夥伴們都可以看得明白。這就是原生的 Dao 的開發方式,實際上還是有點麻煩的。這裡省略了 service 層,這種原生的開發方式我們只要知道怎麼寫就可以了,在實際工作中我們並不會使用這種方式來開發。
2.使用 Mapper 的動態代理的方式開發 DAO
Mapper 介面開發方法只需要程式設計師編寫 Mapper 介面(相當於 Dao 介面),由 mybatis 框架家根據介面定義建立介面的動態代理物件, 代理物件的方式同上邊 Dao 介面實現類方法。這種方式簡單的多,但是有一定的規則。具體規則如下:
Mapper.xml 檔案中的 namespace 於 mapper 介面的類路徑相同。
Mapper 介面方法名和 Mapper.xml 中定義的每個 Statement 的 id 相同。
Mapper 介面方法點的輸入引數型別和 Mapper.xml 中定義的每個 sql 的 parameterType 的型別相同。
Mapper 介面方法的輸出引數型別和 Mapper.xml 中定義的每個 sql 的 resultType 的型別相同。
有了上面的規則,我們就開始編寫 Mapper.xml 對映檔案
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="com.student.mybatis.mapper.UserMapper">
<select id="findUserById" parameterType="int" resultType="user">
select * from user where uid = #{uid}
</select>
<select id="findUserByName" parameterType="string" resultType="user">
select * from user where name LIKE '%${value}%'
</select>
<delete id="deleteUserById" parameterType="int">
delete from user where uid = #{uid}
</delete>
<insert id="insertUser" parameterType="user">
<selectKey keyProperty="uid" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
insert into user(name, sex, address) values (#{name}, #{sex}, #{address});
</insert>
<update id="updateUserById" parameterType="user">
update user set name = #{name} where uid = #{id};
</update>
</mapper>
注意:namespace 要改成 UserMapper 這個介面的全路徑。
UserMapper.java
public interface UserMapper {
User findUserById(Integer id);
List<User> findUserByName(String name);
void insertUser(User user);
}
切記:需要在 SqlMapConfig.xml 中引入 UserMapper.xml 檔案,否則會報錯。
<!--批量引入mapper檔案 -->
<package name="com.student.mybatis.mapper"/>
測試類 TestMapper.java
public class TestMapper {
private SqlSessionFactory factory;
@Before
public void setUP() throws Exception{
String resources = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resources);
factory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testInsertUser(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User();
user.setName("熊二");
user.setSex("1");
user.setAddress("小木屋");
mapper.insertUser(user);
session.commit();
}
@Test
public void testFindUserByName(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> userList = mapper.findUserByName("二");
for (User user :
userList) {
System.out.println(user);
}
}
@Test
public void testFindUserById(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.findUserById(6);
System.out.println(user);
}
}
結果和上面一樣,我就不貼圖了,貼圖佔空間太大。
3. SqlMapConfig.xml
以下是 mybatis 核心配置檔案的完整配置
<?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>
<!-- 引入資料庫的配置檔案-->
<properties resource="db.properties"/>
<!-- 配置自定義別名-->
<typeAliases>
<!-- 定義單個別名-->
<!--<typeAlias type="com.student.mybatis.bean.User" alias="user"/>-->
<!-- 批量定義別名-->
<package name="com.student.mybatis.bean"/>
</typeAliases>
<environments default="development">
<environment id="development">
<!-- mybatis 事務管理器-->
<transactionManager type="JDBC"/>
<!-- mybatis 連線池-->
<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>
<mappers>
<!-- 引入單個 SQL 對映檔案-->
<!--<mapper resource="com/student/mybatis/bean/User.xml"/>-->
<!-- 引入 單個mapper 檔案-->
<!--<mapper class="com.student.mybatis.mapper.UserMapper"/>-->
<!--批量引入mapper檔案 -->
<package name="com.student.mybatis.mapper"/>
</mappers>
</configuration>
配置的內容和順序如下:
- properties(屬性)
- settings(全域性配置引數)
- typeAliases(類型別名)
- typeHandlers(型別處理器)
- objectFactory(物件工廠)
- plugins(外掛)
- environments(環境集合屬性物件)
- environment (環境子屬性物件)
- transactionManager (事務管理)
- dataSource (資料來源)
- mappers(對映器)
4. typeAliases(類型別名)
預設支援別名有很多,這個我就不列舉了,需要的話直接去文件上找即可。
輸入對映和輸出對映
Mapper.xml 對映檔案中定義了操作資料庫的 sql,每個 sql 是一個 statement, 對映檔案是 mybatis 的核心。
1. parameterType(輸入型別)
1.1 傳遞簡單引數
這個就不說了,在上一篇文章中已經詳細講解過了。
1.2 傳遞 pojo 型別
mybatis 使用 ognl 表示式解析物件欄位的值,#{} 或者 ${} 括號中的值為 pojo 屬性名稱。
1.3 傳遞 pojo 包裝物件
開發中通過 pojo 傳遞查詢條件,查詢條件是綜合的查詢條件,不僅包括使用者查詢條件,還包括其他的查詢條件,這個時候可以使用包裝物件傳遞輸入引數。
功能: 根據 name 查詢使用者資訊,查詢條件放到 QueryVo 中的 user 屬性中。
新建類 QueryVo.java
public class QueryVo2 {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
userMapper.xml 中的對映關係
<select id="findUserNameByQueryVo" parameterType="queryVo" resultType="user">
select * from user where name LIKE '%${user.name}%'
</select>
UserMapper.java 介面中新增方法
List<User> findUserNameByQueryVo(QueryVo2 queryVo2);
測試方法
@Test
public void testFindUserByQueryVo(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
QueryVo2 queryVo2 = new QueryVo2();
User user = new User();
user.setName("二");
queryVo2.setUser(user);
List<User> userList = mapper.findUserNameByQueryVo(queryVo2);
for (User u :
userList) {
System.out.println(u);
}
}
2. resultType(結果型別)
這裡我們一般都是在關聯查詢中才會用到,所以在這裡就先不講了,等下面講解關聯查詢的時候,再說輸出對映。別猴急猴急的,當心閃了腰!
動態 SQL
mybatis 主要提供了 4 中標籤,方便我們動態去拼接 sql 語句。
1. if
<select id="findUserByQueryVoId" parameterType="queryVo" resultType="user">
select * from user where 1 = 1
<if test="user.sex != null and user.sex != ''">
and sex = #{user.sex};
</if>
</select>
where 後面拼接 1 = 1,是為了讓 sql 語句執行不報錯。如果不這樣做,當 if 裡面的條件不滿足情況的時候,此時執行的 sql 語句就變成了
select * form user where
很明顯,這個 sql 語句有語法錯誤,所以我們只能在 sql 的後面拼接上 1 = 1, 顯然這樣的做法並不好,如果需要改善這種情況,需要利用我們的下一個關鍵字 where 了。
List<User> findUserByQueryVoId(QueryVo2 queryVo2);
測試方法
@Test
public void testFindUserById2(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
QueryVo2 queryVo2 = new QueryVo2();
User user = new User();
user.setSex("0");
queryVo2.setUser(user);
List<User> userList = mapper.findUserByQueryVoId(queryVo2);
for (User u : userList) {
System.out.println(u);
}
}
結果:
這裡我們和上面一樣,著重看一下執行的 sql 語句。
2. where
為了讓上面的 sql 語句執行正確,我們在 sql 語句後面 拼接了一個恆為 true 的 等式,但是有沒有辦法讓我們的 sql 語句既不用拼接 1 = 1 這種沒意義的等式,又不用讓 if 條件不滿足的情況下不報錯呢 ?答案是肯定的:有!
把上面的 sql 語句做如下更改,此時在執行看一下 sql 語句:
<select id="findUserByQueryVoId" parameterType="queryVo" resultType="user">
select * from user
<where>
<if test="user.sex != null and user.sex != ''">
and sex = #{user.sex};
</if>
</where>
</select>
結果:(看 sql 語句)
你發現了 if 裡面的 and 不見了,沒錯,where 會自動幫我們處理掉條件的第一個 and。如果我們的條件不滿足,where 標籤連 where 這個關鍵字都不會幫我們拼接。現在我再次更改 sql。
<select id="findUserByQueryVoId" parameterType="queryVo" resultType="user">
select * from user
<where>
<if test="user.sex == null and user.sex == ''">
and sex = #{user.sex};
</if>
</where>
</select>
顯然 if 裡面的條件並不成立。執行結果為:(看 sql 語句)
你會發現連 where 都沒有了。這便是 mybatis 動態 sql 的強大之處。
3. foreach
foreach 顧名思義大致就是迴圈,比如當我們想執行如下 sql 語句的時候
select * from user where name like '%二%' and uid in(1,3,9,12);
//查詢 uid 等於 1,3,9,12 並且 name 中有 “二” 的 User 物件
因為不確定括號中的值,所以這裡很明顯需要用到迴圈。初學 mybatis 可能覺得這個 sql 有點複雜,其實不然,只要你能夠寫出 sql, 那麼下面的就不難。
<select id="findUserByForeachQueryVo" parameterType="queryVo" resultType="user">
select * from user
<where>
name like '%${user.name}%'
<foreach collection="ids" item="id" open="and uid in (" separator="," close=")">
#{id}
</foreach>
</where>
</select>
這裡就可以看出來我們封裝 QueryVo 的好處了,基本上只要我們需要什麼,我們都可以在 QueryVo 這個類中去封裝。
QueryVo.java
public class QueryVo2 {
private User user;
private List<Integer> ids;
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
因為是要傳入一組 id 值,所以我們把 id 封裝成一個 List 集合。
collection 這個關鍵字就是問我們要遍歷迴圈的集合名稱,根據 QueryVo,我們知道名稱為 ids。
item 這個關鍵字就是臨時變數,就像 for 迴圈中有一個 i 作為臨時變數一樣。
open 就是開始拼接的部分,從 sql 語句中我們看出來為 “and uid in (”
separator 顯然是分隔符,這裡的分隔符就是 “,”
close 所有的都拼接完成了,就剩個結束標籤了,從 sql 語句中自然可以看出來是 “)”
測試類在此,誰敢猖狂!
@Test
public void testForeach(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
QueryVo2 queryVo2 = new QueryVo2();
List<Integer> list = new ArrayList<>();
User u1 = new User();
u1.setName("二");
queryVo2.setUser(u1);
list.add(1);
list.add(3);
list.add(12);
queryVo2.setIds(list);
List<User> userList = mapper.findUserByForeachQueryVo(queryVo2);
for (User user :
userList) {
System.out.println(user);
}
}
貼出結果,最重要的還是要看控制檯輸出的 sql 語句。其他的都不重要,主要是 sql 語句是否正確,關注點不要錯了。
看 sql 語句
看 sql 語句
看 sql 語句
重要的話說三遍,再不看你就去死吧。
4. 複用 sql 片段
上面就是 mybatis 實現動態 sql 的三個主要以及最簡單的關鍵字,回過頭看 第一個 if 標籤中的 sql 片段,如果 if 標籤中的判斷在很多地方都會用到,這時候我們可以考慮把那個 if 標籤抽離出去,然後在需要用到的地方直接引用即可。
<!-- 抽取 sql -->
<sql id="test_if">
<if test="user.sex != null and user.sex != ''">
and sex = #{user.sex};
</if>
</sql>
<!--引用 sql-->
<select id="findUserByQueryVoId" parameterType="queryVo" resultType="user">
select * from user
<where>
<include refid="test_if"/>
</where>
</select>
如果引用其他的 namespace 中的 sql 片段,則需要加上 namespace,比如
<include refId = "namespace.sql"/>
關聯查詢
這個就厲害了…呃呃呃,不知道怎麼描述,反正就是很厲害了。直接說例子吧。
現在資料庫中有兩張表 user1 和 orders 表
查詢出來的結果為
關聯兩張表查詢(還記得我們說的用別名麼?如果記得,請自己手寫一遍)
//不用別名,查詢出 user1 表和 orders 表中的全部列
select * from user1, orders where user1.id = orders.user_id;
//使用別名,查詢出 user1 表和 orders 表中的全部列
select * from user1 u, orders o where u.id = o.user_id;
查出來結果很明顯有重複的列,我們改正一下 sql 語句, 去除重複的列,以及 orders 中無用的列。
select u.*, o.number, o.createtime, o.note from user1 u, orders o where u.id = o.user_id;
又帶你們複習了一下 sql 語句,一定要鍛鍊手寫 sql 的能力。
1.1 一對一查詢
需求:查詢所有訂單資訊,關聯查詢下單使用者資訊。
注意:因為一個訂單隻會屬於一個使用者,所以從查詢訂單資訊發出關聯查詢使用者資訊是一對一查詢,如果從使用者資訊發出查詢使用者下的訂單資訊則為一對多查詢,因為一個使用者可以有多個訂單。
簡單來說:使用者和訂單之間的關係就是 一對多的關係。
1.1.1 方式一(偷懶,簡單的方式,但是不是 Mybatis 推薦的查詢方式)
使用 resultType,定義訂單資訊實體類,此類中包括了訂單資訊和使用者資訊。
查詢所使用的 sql 語句如下
select o.*, u.username, u.address from orders o, user1 u where o.user_id = u.id;
一、 自定義一個新的實體類 OrderAndUser.java
如果是以訂單為主(根據訂單查使用者,是一對一的關聯查詢),那麼新的實體類就應該繼承 Order,並把 User.java 中需要查詢的屬性全部拷貝過來,根據我們上面的 sql 語句來看,我們只需要查詢使用者表中的 username 和 address 兩個欄位,故我們只拷貝了兩個欄位。
如果你想查詢 User 表中的全部屬性,那就把 User 表中的全部屬性全拷貝過來咯。you happy 就 ok,I am 無所謂。
OrderAndUser. java
public class OrderAndUser extends Order{
private String username; //使用者表中的username欄位
private String address;//使用者表中的address欄位
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
二、定義 sql 對映關係
<select id="findOrderList" resultType="com.student.mybatis.bean.OrderAndUser">
select o.*, u.username, u.address from orders o, user1 u where o.user_id = u.id;
</select>
三、編寫介面方法
List<OrderAndUser> findOrderList();
四、編寫測試方法
@Test
public void testFindAllOrders(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<OrderAndUser> orderList = mapper.findOrderList();
for (OrderAndUser orderAndUser :
orderList) {
System.out.println(orderAndUser);
}
session.close();
}
五、結果演示
友情提示一下,這裡只能用 debug 去看,所以我這裡也是 debug 模式的截圖,看到結果就好了。
上面的這 5 個步驟我這兩篇文章寫了好多遍了,如果還有不懂的同學,請自行回去看第一篇文章。
1.1.2 方式二(正規的方式,也是 Mybatis 推薦的方式,這裡我同樣也推薦你們使用這種)
還記得我們的 sql 標籤中有一個 resultMap 標籤嗎?這個標籤就可以定義專門的一對一查詢。
一、sql 語句
同上,不寫了。有興趣的自己寫一下。
二、定義 POJO 類
在 Order.java 中加入 User 為成員變數。
public class Order {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
//新增的 User1物件
private User1 user;
public User1 getUser() {
return user;
}
public void setUser(User1 user) {
this.user = user;
}
//省略了其他的 get set 方法...
}
三、編寫 UserMapper.xml 對映檔案
<!-- 查詢訂單關聯的使用者資訊 使用resultMap-->
<!-- <!-- type 是主表 order 對應的實體類 order 的別名-->-->
<resultMap id="orderUserResultMap" type="order">
<!-- column 是資料庫中的欄位名, property 是實體物件的屬性名稱-->
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!--使用 association 進行關聯查詢,將查詢的結果對映到 User1 物件中,只關聯查詢了username 和 address 兩個屬性,就只寫這兩個屬性 -->
<association property="user" javaType="com.student.mybatis.bean.User1">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="address" property="address"/>
</association>
</resultMap>
<select id="findOrdersWithUserResultMap" resultMap="orderUserResultMap">
select o.id , o.user_id, o.number, o.createtime, o.note, u.username, u.address from orders o join user1 u on u.id = o.user_id;
</select>
assocition: 表示關聯查詢的單條記錄。
property: 表示關聯查詢的結果集儲存在 Order物件中的 user 屬性中。
javaType: 表示關聯查詢的結果集型別,這裡可以使用別名。
四、編寫測試方法
@Test
public void testResultMap1(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<Order> orderList = mapper.findOrdersWithUserResultMap();
for (Order order :
orderList) {
System.out.println(order);
}
session.close();
}
五、查詢結果
內容有點不夠展示,所以我就只截圖了前兩個,不過前兩個已經夠說明問題了。
1.2 一對多查詢
功能:查詢所有使用者資訊以及使用者對應的訂單。明顯是一個多對一的問題。同樣使用 resultMap 來完成。
一、編寫 sql
select u.*, o.id, o.number, o.createtime, o.note from user1 u left join orders o on u.id = o.user_id;
二、定義 POJO 類
public class User1 {
private Integer id;
private String username;
private String address;
private Date birthday;
private String sex;
//一個使用者對應多個訂單,多以這裡用的是 List 集合
private List<Order> orderList;
public List<Order> getOrderList() {
return orderList;
}
public void setOrderList(List<Order> orderList) {
this.orderList = orderList;
}
}
三、編寫 sql 對映
<!-- 以使用者為主查詢使用者關聯下的訂單資訊-->
<!-- type 是主表 user1 對應的實體類 User1 的別名-->
<resultMap id="userOrderResultMap" type="user1">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="address" property="address"/>
<result column="birthday" property="birthday"/>
<result column="sex" property="sex"/>
<!-- 由於是關聯集合,所以這裡用的是 collection -->
<!-- offType 是從表對應的實體類 Order 的別名-->
<collection property="orderList" ofType="order">
<id column="id" property="id"/>
<result column="user_id" property="userId"/><!-- 這行可以不寫,因為從 user 表中我們已經查出了 id,重複展示出來沒有什麼意義-->
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
</collection>
</resultMap>
<!-- 這裡的 sql 語句同樣用到了 連線查詢 -->
<select id="findUsersWithOrderResultMap" resultMap="userOrderResultMap">
select u.*, o.id, o.number, o.createtime, o.note from user1 u left join orders o on u.id = o.user_id;
</select>
property=”orderList” : 關聯查詢的結果儲存在 User1 物件中的哪一個屬性上,
ofType=”order” :指定關聯查詢的結果中的物件型別,即 List 中的物件型別,這裡可以使用別名,同樣也可以使用全限定名。
四、編寫測試方法
@Test
public void testResultMap2(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User1> user1List = mapper.findUsersWithOrderResultMap();
for (User1 user :
user1List) {
System.out.println(user);
}
}
五、執行結果
結果太多,我就不展開看了,具體的情況你們自己看截圖就可以了。大部分人都應該是看的明白的。
結尾
這篇文章寫到這裡,關於 mybatis 的大部分知識都已經完結了。我翻了一下,這篇文章好像有點長。不過不影響閱讀,這篇文章的內容是由淺入深的,所以我覺得理解起來也不是很困難。好啦,寫到這裡,也該結束了,太長了看起來也累。本人能力有限,所以只能寫出這樣的文章,寫作過程中,難免會出現錯誤,如果有錯誤還請各位能夠指出,當然也歡迎志同道合的小夥伴們留言交流。