MyBatis框架安排
目錄
3. 第三天(涉及到的應用都會要用):
1. mybatis連接池以及事務控制
-
連接池:連接池就是一個存儲連接對象的容器,mybatis可以從這個容器從獲取連接數據庫的連接對象,並且這個容器是線程安全的,是爲了避免同時從一個容器中獲取到相同的連接對象,因此該容器還必須實現隊列的特性,先進先出(用的先從頭部取,用完從尾部添加,形成循環隊列)
-
mybatis連接池以及分析
mybatis提供了三種連接池配置方式
配置位置:
配置主配置文件SqlMapConfig.xml的dataSource標籤下的type屬性,有以下三種取值:POOLED:採用傳統的javax.sql.DataSource規範中的連接池,mybatis有針對此規範的實現 UNPOOLED:採用傳統的連接方式,雖然也實現了javax.sql.DataSource規範,但沒有池的概念 JNDI:採用服務器提供的JDNI技術實現來獲取DataSource對象,不同的服務器能拿到不同的對象 注意:如果不是web或maven的war工程,是不能使用的,我們平時所使用的tomcat服務器,採用的連接池是dbcp連接池。
可以再idea開發工具中Gtrl+N搜索PoolDataSource和UNPoolDataSource查看源碼
PoolDataSource與UnpooledDataSource:源碼的簡單分析
POOLED:是在連接池中獲取連接對象的,連接池又分爲空閒連接池(idleConnections)和活動連接池(activeConnections)兩部分,如果空閒連接已經沒了(分配完了),則取活動連接池裏面取出來,當活動連接池都滿了(即每個連接都在使用當中)則會取出最早(也叫oldestActiveConnection)進入活動連接池的那個連接對象加以改造,然後取出使用,當用完之後,將連接對象返回連接池中。
UNPOOLED:比較簡單,則是直接從配置文件加載配置信息,然後創建連接Connection,沒有連接池,所以用完之後直接關閉銷燬了。
-
mybatis事務控制的分析
- 回顧事務常見的面試題
- 什麼是事務
- 事務的四大特性ACID
- 不考慮隔離性會產生的3個問題
- 四種隔離界級別
答案:- _ -
- mybatis是可以支持事務的自動提交的,如下
- 回顧事務常見的面試題
2. mybatis基於xml的動態sql語句使用
-
sql注入:是爲了防止黑客等不法分子惡意注入表單,拼接sql語句去匹配數據庫,導致被數據庫正常解析而引起的後臺信息泄露問題。
-
動態sql:在jdbc中使用preparedStatement對象處理,又稱SQL的預編譯處理,請看:
-
按照條件去查詢
<!--根據條件查詢查詢--> <select id="findByCondition" resultType="uSER" resultMap="userMap"> <!-- 取別名來和實體類產生關係 --> SELECT * from USER where 1=1 <if test="userName!=null"> and username = #{userName}</if> </select>
其中 1 = 1可以直接使用where標籤替換,如
<!--根據條件查詢查詢--> <select id="findByCondition" resultType="uSER" resultMap="userMap"> <!-- 取別名來和實體類產生關係 --> SELECT * from USER <where> <if test="userName!=null"> and username = #{userName} </if> </where> </select>
還可以給出多個查詢條件
<!--根據條件查詢查詢--> <select id="findByCondition" resultType="uSER" resultMap="userMap"> <!-- 取別名來和實體類產生關係 --> SELECT * from USER <where> <if test="userName!=null"> and username = #{userName} </if> <if test="userSex!=null"> and sex = #{userSex} </if> </where> </select>
測試:
@org.junit.Test public void testFindCondition() throws IOException { User user = new User(); user.setUserName("傳智播客"); user.setUserSex("男"); List<User> users = userDao.findByCondition(user); for (User u: users) { System.out.println(u); } }
3. mybatis的多表操作
-
表與表之間的關係
- 多對一:用戶和訂單就是一對多訂單和用戶就是多對一
- 一對多:一個用戶可以下多個訂單多個訂單屬於同一個用戶
- 一對一:人和身份證號就是一對一,一個人只能有一個身份證號一個身份證號
- 多對多:只能屬於一個人老師和學生之間就是多對多一個學生可以被多個老師教過一個老師可以交多個學生
- 特例:
如果拿出每一個訂單,他都只能屬於一個用戶。所以Mybatis就把多對一看成了一對一。I
-
示例1:用戶和賬戶
一個用戶可以有多個賬戶
一個賬戶只能屬於一個用戶(多個賬戶也可以屬於同一個用戶)
步驟:- 建立兩張表:用戶表,賬戶表
讓用戶表和賬戶表之間具備一對多的關係:需要使用外鍵在賬戶表中添加 - 建立兩個實體類:用戶實體類和賬戶實體類讓用戶和賬戶的實體類能體現出來一對多的關係
- 建立兩個配置文件用戶的配置文件賬戶的配置文件
- 實現配置:
當我們查詢用戶時,可以同時得到用戶下所包含的賬戶信息當我們查詢賬戶時,可以同時得到賬戶的所屬用戶信息
- 建立兩張表:用戶表,賬戶表
案例需求1:一對一查詢(多對一)
本次案例主要以最爲簡單的用戶和賬戶的模型來分析Mybatis多表關係。用戶爲User 表,賬戶爲Account 表。一個用戶(User)可以有多個賬戶(Account)。具體關係如下:
查詢所有賬戶信息,關聯查詢下單用戶信息。
注意:
因爲一個賬戶信息只能供某個用戶使用,所以從查詢賬戶信息出發關聯查詢用戶信息爲一對一查詢。
如果從用戶信息出發查詢用戶下的賬戶信息則爲一對多查詢,因爲一個用戶可以有多個賬戶。
方案一:
-
準備好Account類
public class Account implements Serializable{ private Integer id; private Integer uid; private Double money; private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public String toString() { return "Account{" + "id=" + id + ", uid=" + uid + ", money=" + money + ", user=" + user + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getUid() { return uid; } public void setUid(Integer uid) { this.uid = uid; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } }
-
編寫SQL語句
SELECT account.*, user.username, user.address FROM account, user WHERE account.uid = user.id
-
創建AccountUser繼承Account
package com.liuzeyu.domin; public class AccountUser extends Account{ private String username; private String address; @Override public String toString() { return super.toString() +" AccountUser{" + "username='" + username + '\'' + ", 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; } }
-
定義賬戶的持久層 Dao 接口
public interface IAccountDao { /** * 查詢所有賬戶,同時獲取賬戶的所屬用戶名稱以及它的地址信息 */ List<AccountUser> findAll(); }
-
定義 AccountDao.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.liuzeyu.dao.IAccountDao"> <!-- 配置查詢所有操作--> <select id="findAll" resultType="accountuser"> select a.*,u.username,u.address from account a,user u where a.uid =u.id; </select> </mapper
注意:因爲上面查詢的結果中包含了賬戶信息同時還包含了用戶信息,所以我們的返回值類型 returnType 的值設置爲 AccountUser 類型,這樣就可以接收賬戶信息和用戶信息了
-
創建 AccountTest 測試類
/* <p>Title: MybastisCRUDTest</p> * <p>Description: 一對多賬戶的操作</p> * */ public class AccountTest { private InputStream in ; private SqlSessionFactory factory; private SqlSession session; private IAccountDao accountDao; @Test public void testFindAll() { //6.執行操作 List<AccountUser> accountusers = accountDao.findAll(); for(AccountUser au : accountusers) { System.out.println(au); } @Before //測試開始前執行 public void init() throws IOException { //1.解析xml配置文件 is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.構建工廠 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); //3.工廠生產SqlSession對象 session = factory.openSession(); //session = factory.openSession(true); //設置事物的自動提交,可以不用//session.commit(); //4.創建代理Dao的對象 accountDao= session.getMapper(IAccountDao .class); } @After //測試結束後執行 public void destory(){ //session.commit(); //7.釋放資源 if( session != null){ session.close(); } if( is != null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } }
方案2:
使用 resultMap,定義專門的 resultMap 用於映射一對一查詢結果。 通過面向對象的包含關係可以得知,我們可以在 Account 類中加入一個 User 類的對象來代表這個賬戶 是哪個用戶的。
-
Account類中加入User對象
package com.liuzeyu.domin; import java.io.Serializable; public class Account implements Serializable{ private Integer id; private Integer uid; private Double money; private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public String toString() { return "Account{" + "id=" + id + ", uid=" + uid + ", money=" + money + ", user=" + user + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getUid() { return uid; } public void setUid(Integer uid) { this.uid = uid; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } }
-
修改 AccountDao 接口中的方法
public interface IAccountDao { /** * 查詢所有賬戶,同時獲取賬戶的所屬用戶名稱以及它的地址信息 * @return */ * List<Account> findAll(); * } 注意:第二種方式,將返回值改 爲了 Account 類型而沒有再繼承與Account。 因爲 Account 類中包含了一個 User 類的對象,它可以封裝賬戶所對應的用戶信息。
-
重新定義 AccountDao.xml 文
<resultMap id="rstAc" type="account"> <id property="id" column="id"/> <result property="uid" column="uid"/> <result property="money" column="money"/> <!--javaType="user"映射後封裝的結果集 外鍵:uid--> <association property="user" column="uid" javaType="user"> <id property="id" column="id"/> <id property="username" column="username"/> <id property="address" column="address"/> <id property="sex" column="sex"/> <id property="birthday" column="birthday"/> </association> </resultMap> <!-- 打印user表數據 --> <select id="findAll" resultMap="rstAc"> SELECT a.*,u.username,u.address FROM account a,USER u WHERE a.`UID`=u.`id`; </select>
-
在 AccountTest 類中加入測試方法
@Test public void testFindAll() { List<Account> accounts = accountDao.findAll(); for(Account au : accounts) { System.out.println(au); System.out.println(au.getUser()); } }
案例需求2:一對多查詢
本案例查詢所有用戶信息及用戶關聯的賬戶信息。
分析: 用戶信息和他的賬戶信息爲一對多關係,並且查詢過程中如果用戶沒有賬戶信息,此時也要將用戶信息 查詢出來,我們想到了左外連接查詢比較合適
-
編寫 SQL 語句
SELECT u.*, acc.id id, acc.uid, acc.money FROM user u LEFT JOIN account acc ON u.id = acc.uid 測試該 SQL 語句在 MySQL 客戶端工具的查詢結果如下
-
User 類加入 List< Account >
package com.liuzeyu.domin; import java.util.Date; import java.util.List; public class User { private Integer id; private String address; private String username; private String sex; private Date birthday; private List<Account> accounts; public List<Account> getAccounts() { return accounts; } public void setAccounts(List<Account> accounts) { this.accounts = accounts; } @Override public String toString() { return "User{" + "id=" + id + ", address='" + address + '\'' + ", username='" + username + '\'' + ", sex='" + sex + '\'' + ", birthday=" + birthday + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } 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; } }
-
用戶持久層 Dao 接口中加入查詢方法
/** * 查詢所有用戶,同時獲取出每個用戶下的所有賬戶信息 * @return */ * List<User> findAll();
-
用戶持久層 Dao 映射文件配置
<resultMap id="userMap" type="user"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="address" column="address"/> <result property="sex" column="sex"/> <result property="birthday" column="birthday"/> <collection property="accounts" ofType="account"> <!-- collection 是用於建立一對多中集合屬性的對應關係 ofType 用於指定集合元素的數據類型 --> <id property="id" column="id"/> <id property="uid" column="uid"/> <id property="money" column="money"/> </collection> </resultMap> <select id="findAll" resultMap="userMap"> select * from user u left outer join account a on a.uid = u.id; </select> 屬性解釋: collection 部分定義了用戶關聯的賬戶信息。表示關聯查詢結果集 property="accList" :關聯查詢的結果集存儲在 User 對象的上哪個屬性。 ofType="account" :指定關聯查詢的結果集中的對象類型即List中的對象類型。此處可以使用別名,也可以使用全限定名。
- 測試方法:
@Test public void testFindAll() { //6.執行操作 List<User> users = userDao.findAll(); for(User user : users) { System.out.println("-------每個用戶的內容---------"); System.out.println(user); System.out.println(user.getAccounts()); } }
問題:resultMap問題
可見如果再resultMap中的collection下封裝account,id的column如果取aid和取id是兩種不同的結果,爲什麼會出現這種情況呢?
原因要result子元素的作用說起:
result 子元素:
◆ property 屬性:映射數據庫列的實體對象的屬性。
◆ column 屬性:數據庫列名或別名。
1)取id:由於和account表的id對應上了,所以是肯定是可以查到結果,但是封裝成account對象的時候遇到和user表相同的id,mybatis就直接那它當作account的id進行封裝了。
2)==取aid:==由於和數據庫的列名沒有對應上,又沒有取別名,因此是可以最後的時候id沒有被封裝進去。
3)緊接着我們改一下數據庫的字段名:
取aid:
測試結果:
所以此時和數據庫的account表的字段名對應上了,而且沒有和user表的id重複,封裝對象成功!!!
- 示例2:用戶和角色(多對多查詢)
一個用戶可以有多個角色
一個角色可以賦予多個用戶
步驟:- 建立兩張表:用戶表,角色表
讓用戶表和角色表具有多對多的關係。需要使用中間表,中間表中包含各自的主鍵,在中間表中是外鍵。 - 建立兩個實體類:用戶實體類和角色實體類讓用戶和角色的實體類能體現出來多對多的關係各自包含對方一個集合引用
- 建立兩個配置文件
用戶的配置文件角色的配置文件4、實現配置:
當我們查詢用戶時,可以同時得到用戶所包含的角色信息當我們查詢角色時,可以同時得到角色的所賦予的用戶信息
- 建立兩張表:用戶表,角色表
案例需求1:查詢角色,獲取角色下所屬的用戶信息
實現查詢所有用戶信息並關聯查詢出每個用戶的角色列表。
從 User 出發,我們也可以發現一個用戶可以具有多個角色,這樣用戶到角色的關係也還是一對多關係。
這樣 我們就可以認爲 User 與 Role 的多對多關係,可以被拆解成兩個一對多關係來實現
-
SQL語句準備
DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL auto_increment, `username` varchar(32) NOT NULL COMMENT '用戶名稱', `birthday` datetime default NULL COMMENT '生日', `sex` char(1) default NULL COMMENT '性別', `address` varchar(256) default NULL COMMENT '地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (41,'老王','2018-02-27 17:47:08','男','北京'),(42,'小二王','2018-03-02 15:09:37','女','北京金燕龍'),(43,'小二王','2018-03-04 11:34:34','女','北京金燕龍'),(45,'傳智播客','2018-03-04 12:04:06','男','北京金燕龍'),(46,'老王','2018-03-07 17:37:26','男','北京'),(48,'小馬寶莉','2018-03-08 11:44:00','女','北京修正'); DROP TABLE IF EXISTS `role`; CREATE TABLE `role` ( `ID` int(11) NOT NULL COMMENT '編號', `ROLE_NAME` varchar(30) default NULL COMMENT '角色名稱', `ROLE_DESC` varchar(60) default NULL COMMENT '角色描述', PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `role`(`ID`,`ROLE_NAME`,`ROLE_DESC`) values (1,'院長','管理整個學院'),(2,'總裁','管理整個公司'),(3,'校長','管理整個學校'); DROP TABLE IF EXISTS `user_role`; CREATE TABLE `user_role` ( `UID` int(11) NOT NULL COMMENT '用戶編號', `RID` int(11) NOT NULL COMMENT '角色編號', PRIMARY KEY (`UID`,`RID`), KEY `FK_Reference_10` (`RID`), CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `role` (`ID`), CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `user_role`(`UID`,`RID`) values (41,1),(45,1),(41,2);
-
角色實體類Role
public class Role { private Integer roleId; private String roleName; private String roleDesc; private List<User> users; public List<User> getUsers() { return users; } public void setUsers(List<User> users) { this.users = users; } @Override public String toString() { return "Role{" + "roleId=" + roleId + ", roleName='" + roleName + '\'' + ", roleDesc='" + roleDesc + '\'' + '}'; } public Integer getRoleId() { return roleId; } public void setRoleId(Integer roleId) { this.roleId = roleId; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public String getRoleDesc() { return roleDesc; } public void setRoleDesc(String roleDesc) { this.roleDesc = roleDesc; } }
- dao層接口
public interface IRoleDao { //檢索表 public List<Role> findAll(); }
- 映射配置文件
<?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.liuzeyu.dao.IRoleDao"> <resultMap id="roleMap" type="role"> <id property="roleId" column="id"/> <result property="roleName" column="role_name"/> <result property="roleDesc" column="role_desc"/> <collection property="users" ofType="user"> <id property="id" column="id"/> <id property="username" column="username"/> <id property="address" column="address"/> <id property="sex" column="sex"/> <id property="birthday" column="birthday"/> </collection> </resultMap> <select id="findAll" resultMap="roleMap"> SELECT u.* ,r.id AS rid ,r.role_name,r.role_desc FROM role r LEFT OUTER JOIN user_role ur ON r.id = ur.rid LEFT OUTER JOIN USER u ON u.id = ur.uid; </select> </mapper>
其中SQL語句的編寫是難點。
- 測試方法
@org.junit.Test public void testSelect() throws IOException { //5.使用代理對象執行方法 List<Role> roles = roleDao.findAll(); //6.遍歷對象 for (Role role : roles) { System.out.println(role); System.out.println("--------"); System.out.println(role.getUsers()); } }
數據庫查詢結果:
案例需求2:查詢用戶,獲取用戶下所屬的角色信息
在案例1的基礎上對比學習:- 在Role類中新增User屬性,並新增getXXX setXXX方法
private List<User> users; public List<User> getUsers() { return users; } public void setUsers(List<User> users) { this.users = users; }
- 映射配置文件
<mapper namespace="com.liuzeyu.dao.IRoleDao"> <resultMap id="roleMap" type="role"> <id property="roleId" column="id"/> <result property="roleName" column="role_name"/> <result property="roleDesc" column="role_desc"/> <collection property="users" ofType="user"> <id property="id" column="id"/> <id property="username" column="username"/> <id property="address" column="address"/> <id property="sex" column="sex"/> <id property="birthday" column="birthday"/> </collection> </resultMap> <select id="findAll" resultMap="roleMap"> SELECT u.* ,r.id AS rid ,r.role_name,r.role_desc FROM role r LEFT OUTER JOIN user_role ur ON r.id = ur.rid LEFT OUTER JOIN USER u ON u.id = ur.uid; </select> </mapper>
- dao接口實現
public List<User> findAll();
- 測試方法
@org.junit.Test public void testU2R() throws IOException { //5.使用代理對象執行方法 List<User> users = userDao.findAll(); //6.遍歷對象 for (User user : users) { System.out.println(user); System.out.println("--------"); System.out.println(user.getRoles()); } }
數據庫查詢結果:
4. 擴展知識:JNDI
-
JNDI概述和原理
- 概述:JNDI(Java Naming and Directory Interface,Java命名和目錄接口)是SUN公司提供的一種標準的Java命名系統接口.
- JNDI的原理就是模仿windows系統的註冊表,內部數據的存在形式是Map,windows註冊表就是一個Map的結構
- JNDI的底層是一個HashMap結構,將名稱和對象使用鍵值對一一對應,這種關係的搭建與Map體系緊緊相連。
- Windows 系統註冊表和Tocat服務器的數據存放對比
-
JNDI搭建maven的war工程
- 新建一個maven 工程, 選擇web骨架
- 講將原來mybatis的類和方法導入
-
再webapp目錄下新建META-INF文件夾,將context.xml(相當於jndi得配置文件)導入
<?xml version="1.0" encoding="UTF-8"?> <Context> --> <Resource <!-- jdbc/eesy_mybatis可以隨便取值(key)--> name="jdbc/eesy_mybatis" <!-- type 存入得對象(value)--> type="javax.sql.DataSource" <!-- --作者是一個tomcat容器 --> auth="Container" maxActive="20" maxWait="10000" maxIdle="5" username="root" password="809080" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/eesy_mybatis" /> </Context>
-
將主配置文件得JDBC連接池部分修改爲
<!-- JDBC連接池 --> <dataSource type="JNDI"> <!-- java:comp/env/jdbc/ 路徑固定寫法 ;name:eesy_mybatis--> <property name="data_source" value="java:comp/env/jdbc/eesy_mybatis"/> </dataSource>
-
測試test 得findAll方法,發現拋出異常
-
使用JNDI數據源
配置完tomcat服務器後
在webapp下得index.jsp添加Java代碼:<% InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); SqlSession sqlSession = factory.openSession(); IUserDao dao = sqlSession.getMapper(IUserDao.class); List<User> users = dao.findAll(); for (User user : users) { System.out.println(user); } is.close(); sqlSession.close(); %>
-
在瀏覽器訪問:localhost/day04/index.jsp
可以正常使用JNDI連接
- 解釋說明
爲什麼5會拋出異常,而使用index.jsp界面訪問卻不會?
因爲這是一個tomcat項目,.jsp文件會被編譯成成.class,然後被tomcat運行,因爲jsp本質上就是一個servlet,運行與tomcat容器中。
而一個findAll測試類,並不會被tomcat解析,因爲它既不是servlet,也不是jsp,因此會拋出上述異常。