mybatis-day03-自帶數據源、事務、動態sql、多表配置

一、今日內容

  1. mybatis自帶數據源
  2. 事務問題
  3. 動態的sql語句:if標籤和循環標籤 (重點)
  4. 多表之間的關係配置 一對一 一對多 多對多 (重點)

二、mybatis自帶數據源

<!--數據庫環境
        default:指定默認環境
    -->
    <environments default="development">
        <!--環境唯一的標誌-->
        <environment id="development">
            <!--事務管理:使用jdbc的事務-->
            <transactionManager type="JDBC"/>
            <!--
                dataSource:數據源(又稱數據庫連接池)
                type="POOLED":數據源類型配置 用得最多★
                    POOLED: 使用mybatis的自帶數據源配置
                    UNPOOLED: 不使用數據源配置 (非數據庫連接池)   使用Connection操作數據庫
                    JNDI: JNDI服務 數據源配置(麻煩,少用)
            -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatisdb_331"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

在這裏插入圖片描述

三、事務

原始jdbc的事務

關鍵行:
24行: 設置事務手動提交(不能自動提交)
36行: 提交事務
40行: 異常回滾事務
49行: 還原事務默認自動提交的狀態

public class TestJDBC {

    @Test
    public void test(){

        List<User> userList = new ArrayList<>();

        //1. 註冊驅動
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        String url = "jdbc:mysql://localhost:3306/mybatisdb_331";
        String username = "root";
        String password = "root";
        Connection conn = null;
        PreparedStatement pst = null;
        ResultSet rs = null;
        try {
            //2. 獲取連接
            conn = DriverManager.getConnection(url, username ,password);
            //事務1 :設置事務手動提交(不能自動提交)
            conn.setAutoCommit(false);
            // 3. SQL語句
            String sql1 = "insert into ......";
            String sql2 = "insert into ......";
            //4. 創建statement對象: Statement , PreparedStatement
            pst = conn.prepareStatement(sql1);
            //5. 執行SQL語句,返回結果集
            pst.executeUpdate();

            pst = conn.prepareStatement(sql2);
            pst.executeUpdate();
            //事務2:提交事務
            conn.commit();
        } catch (SQLException e) {
            //事務3:出現異常,回顧
            try {
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            //事務4: 還原狀態,設置事務爲自動提交
            if(conn != null){
                try {
                    conn.setAutoCommit(true);
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            //7. 釋放資源: 先開後關
            if(rs != null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(pst != null){
                try {
                    pst.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

        }

        //打印結果
        for (User user : userList) {
            System.out.println(user);
        }

    }
}

mybatis內置的事務管理(幫你寫好了)

通過日誌查看,原理和jdbc原生事務一樣
在這裏插入圖片描述

之前寫修改都要手動sqlSession.commit();太麻煩,其實openSession()時傳入參數true設置默認自動提交,就不需要每次commit了

openSession(true),避免手動提交事務(壞處:關閉了事務)

在這裏插入圖片描述
在這裏插入圖片描述

四、動態sql語句

今日pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.ahpu</groupId>
    <artifactId>mybatis_day03_5_many2many</artifactId>
    <version>1.0-SNAPSHOT</version>

    
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

    </dependencies>


</project>

1、問題

UserDao.xml中的sql

 <!--多條件查詢-->
    <select id="findByCondition" resultMap="users" resultType="user">
        select * from user where uname like "%"#{username}"%" and sex=#{sex}
    </select>

測試類中的方法

@Test
    public void testFindByCondition(){
        //獲取sqlSession對象
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml")).openSession();
        //獲取動態代理對象
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = new User();
        //user.setUsername("英");//註釋本行報錯
        user.setSex("女");//只註釋本行 不報錯 但查詢結果爲null
        List<User> list = userDao.findByCondition(user);
        for (User u : list) {
            System.out.println(u);
        }
        //關閉資源
        sqlSession.close();
    }
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'null"%" and sex='女'' at line 1

當需要多個參數,卻傳入少數幾個參數,也即有需要傳入的參數爲null時,要麼報錯,要麼查不出結果。都是傳參不過導致sql語句拼接後出了問題

解決方法就是使用動態sql語句,sql語句內也寫上邏輯判斷 應該是OGNL

2、if語句

寫法1: 技巧性寫法 1=1 然後都加and 無論咋拼接都沒有sql語法錯誤

 <select id="findByCondition" resultMap="users" resultType="user">
        select * from user where 1=1
        <if test="username != null">
            and uname like "%"#{username}"%"
        </if>
        <if test="sex != null">
            and sex=#{sex}
        </if>
 </select>

寫法2: mybatis內部解決方案 不寫where而使用where標籤

3、where語句

<!--<where>標籤只會幫我們處理第一個and
    因此都加上and最安全
-->
<select id="findByCondition" resultMap="users" resultType="user">
    select * from user
    <where>
        <if test="username != null">
            and uname like "%"#{username}"%"
        </if>
        <if test="sex != null">
            and sex=#{sex}
        </if>
    </where>
</select>

4、SQL片段

重複的sql語句不想總是寫 可以提取出爲sql片段

<!--sql片段
          把重複的sql語句提取出來,需要使用時引用即可
          id:唯一標識
          文本:sql語句
    -->
    <sql id="select_user">select * from user</sql>

    <select id="findByCondition" resultMap="users" resultType="user">
        -- 引入sql片段
        <include refid="select_user"></include>
        <where>
            <if test="username != null">
                and uname like "%"#{username}"%"
            </if>
            <if test="sex != null">
                and sex=#{sex}
            </if>
        </where>
    </select>

以後遇到很長且需要重複使用的sql語句時可以這麼做

5、foreach語句

根據integer數組ids刪除指定多個id的記錄

根據數組刪除

xml中的sql

<delete id="delByArray" parameterType="int[]">
        delete from user where
        <!--
          foreach循環標籤
              collection: 參數類型 取值:數組用 array 集合用: list
              open: 前綴  (以...開始)
              close: 後綴 (以...結束)
              separator: 分隔符
              item: 循環中的每一個對象 (名字隨便取)
              index: 循環中的索引(一般不用)

        -->
        <!-- uid in (1,2,3) -->
        <foreach collection="array" open="uid in (" close=")" separator="," item="id">
            #{id}
        </foreach>
    </delete>

測試代碼:

 @Test
    public void testDelByArray(){
        //獲取sqlSession對象
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml")).openSession();
        //獲取動態代理對象
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        Integer[] ids=new Integer[]{7,15};
        userDao.delByArray(ids);
        sqlSession.commit();
        //關閉資源
        sqlSession.close();
    }

根據集合刪除

同上例子
xml中的sql

<delete id="delByList" parameterType="list">
        DELETE FROM USER WHERE
        /*uid in (1,2,3)*/
        <foreach collection="list" open="uid in (" close=")" separator="," item="id">
            #{id}
        </foreach>
    </delete>

測試代碼

@Test
    public void testDelByList(){
        //獲取sqlSession對象
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml")).openSession();
        //獲取動態代理對象
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<Integer> list=new ArrayList<Integer>();
        list.add(20);list.add(21);
        userDao.delByList(list);
        sqlSession.commit();
        //關閉資源
        sqlSession.close();
    }

五、多表關聯(查詢所有數據,想法映射到每個屬性)

1、一對一

新建account表:

create table account(
	id int primary key auto_increment,
	name varchar(50) unique,
	money float(8,2),
	u_id int,
	foreign key(u_id) references user(uid)	
);

插入數據:
在這裏插入圖片描述
在這裏插入圖片描述
需求: 一個賬戶對應一個用戶,查詢所有賬戶,同時包含其對應的那個用戶

一對一查詢1: 新建pojo 繼承自A類 屬性同B類 (超級麻煩寫法)

就是新建一個javaBean,使得包含兩張表對應的所有屬性,可以繼承自一個類(java不支持多繼承,因此另一個類屬性只得慢慢寫了)
Account.java

package cn.ahpu.domain;

/**
 * @author 寒面銀槍
 * @create 2020-01-08 16:52
 */
public class Account {
    private Integer id;
    private String name;
    private float money;
    private Integer u_id;
    //省略get/set
    @Override
    public String toString() {
        return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + ", u_id=" + u_id + '}';
    }
}

AccountUser.java(額外專門寫一個pojo 煩)

/**
 * 繼承Account便擁有了Account的所有屬性
 * 單獨添加user屬性
 */
public class AccountUser extends Account {
    //再寫上所有user屬性
    private Integer uid;
    private String username;
    private String password;
    private String address;
    private String sex;
    private Date birthday;

    //省略get/set/toString
}

AccountDao.java

	//查詢所有的賬戶 包含對應用戶信息
    public List<AccountUser> findAllAccountUser();

UserDao.xml (sql語句配置)

<mapper namespace="cn.ahpu.dao.AccountDao">

    <resultMap id="accountUsers" type="accountUser">
        <result column="uname" property="username"></result>
    </resultMap>
    
    <select id="findAllAccountUser" resultMap="accountUsers">
        select * from account a,user u where a.u_id=u.uid
    </select>
</mapper>

TestMybatisOne2One.java 測試類

@Test
    public void testFindAllAccountUser(){
        //獲取sqlSession對象
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml")).openSession();
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
        List<AccountUser> list = accountDao.findAllAccountUser();
        //AccountUser繼承自Account 沒事!下可以接着寫Account
        for (Account au : list) {
            System.out.println(au);
        }
        //關閉資源
        sqlSession.close();
    }

運行結果:

AccountUser{uid=1, username='zhangsan', password='zhangsan', address='蜀山', sex='男', birthday=null} Account{id=1, name='1234564156', money=100.0, u_id=1}
AccountUser{uid=2, username='lisi', password='lisi', address='天墉城', sex='男', birthday=null} Account{id=2, name='242415', money=1000.0, u_id=2}
AccountUser{uid=10, username='天河', password='tianhe', address='青鸞峯', sex='男', birthday=Tue Jan 07 16:15:23 CST 2020} Account{id=3, name='24235698', money=200.0, u_id=10}

一對一查詢2: 直接在一方寫另一方的類對象爲屬性(稍正常寫法)

Account.java

public class Account {
    private Integer id;
    private String name;
    private float money;
    private Integer u_id;
    //一個賬戶對應一個用戶
    private User user;
    //省略get/set
}

沒有AccountUser了

AccountDao 接口方法

    //查詢所有的賬戶 包含對應用戶信息
    public List<Account> findALL();

AccountDao.xml sql配置 ★ 唯一麻煩都是倒騰返回值

<!--查詢所有用戶 需要保護所屬客戶信息 但不必創建新的pojo-->
    <!--複雜pojo需要配置下-->
    <resultMap id="accounts" type="Account">
        <!--配置時property和column完全相同的都可省略-->
        <!--<id property="id" column="id"></id>
        <result column="name" property="name"></result>
        <result column="money" property="money"></result>
        <result column="u_id" property="u_id"></result>-->
        <!--映射User中的屬性 也是User與user表的映射-->
        <!--property和column都不完全相同 都不能省略-->
        <result column="uid" property="user.id"></result>
        <result column="uname" property="user.username"></result>
        <result column="password" property="user.password"></result>
        <result column="sex" property="user.sex"></result>
        <result column="address" property="user.address"></result>
        <result column="birthday" property="user.birthday"></result>
    </resultMap>
    <select id="findALL" resultMap="accounts">
        select * from account a,user u where a.u_id=u.uid
    </select>

TestMybatisOne2One 測試方法

@Test
    public void testFindALL(){
        //獲取sqlSession對象
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml")).openSession();
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
        List<Account> list = accountDao.findALL();
        for (Account a : list) {
            System.out.println(a);
        }
        //關閉資源
        sqlSession.close();
    }

一對一查詢3: resultMap中使用association(全屬性映射 今後寫法★ 次日改進)

**所有屬性都得一一映射 一個都不能省略 好處是結構很清晰 今後都用此 累就累點吧 **

<!--方法3 更改改 resultMap 的映射
    用到了 association 則所有的映射不管相同不相同 都得一個個配置 -->
<resultMap id="accounts" type="Account">
	<!--property與column完全相同的也需要映射-->
    <id property="id" column="id"></id>
    <result column="name" property="name"></result>
    <result column="money" property="money"></result>
    <result column="u_id" property="u_id"></result>
    <!--映射單個對象User-->
    <association property="user" javaType="user">
        <id property="id" column="uid"></id>
        <result column="uname" property="username"></result>
        <!--property與column完全相同的也需要映射-->
        <result column="password" property="password"></result>
        <result column="sex" property="sex"></result>
        <result column="address" property="address"></result>
        <result column="birthday" property="birthday"></result>
    </association>
</resultMap>
<select id="findALL" resultMap="accounts">
    select * from account a,user u where a.u_id=u.uid
</select>

其他同二

2、一對多

現在的情境是一個用戶可以有多個賬戶
一個用戶對應多個賬戶,查詢用戶同時查到他下面的所有賬戶
上面是操作Account,接下來則是操作User
UserDao 接口方法

//返回所有user對象 包含用戶對應的賬戶信息
public List<User> findAll();

UserDao.xml sql配置 ★ 以後多表操作不管那麼多 都給全屬性配置上 此處是一對多配置

<mapper namespace="cn.ahpu.dao.UserDao">
    <resultMap id="users" type="user">
        <id property="id" column="uid"></id>
        <result property="username" column="uname"></result>
        <result property="password" column="password"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
        <result property="birthday" column="birthday"></result>
        <!--List<Account>映射 collection
            property:屬性名
            ofType:集合中的元素類型  (類似 association中的 javaType)
        -->
        <collection property="accounts" ofType="account">
            <id column="id" property="id"></id>
            <result property="name" column="name"></result>
            <result property="money" column="money"></result>
            <result property="u_id" column="u_id"></result>
        </collection>
    </resultMap>
    <select id="findAll" resultMap="users">
        /*select * from user,account where u_id=uid; 沒有賬戶的不符合條件的無法顯示 記得where變on*/
        select * from user left join account on u_id=uid
    </select>
</mapper>

TestMybatisOne2Many 測試方法

/**
     * 查詢所有用戶 包含賬戶信息
     */
    @Test
    public void testFindAll(){
        //獲取sqlSession對象
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml")).openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> list = userDao.findAll();
        for (User user : list) {
            System.out.println(user);
        }
        //關閉資源
        sqlSession.close();
    }

3、多對多

增加角色,用戶和角色多對多(必須引入中間表)
用戶與角色多對多,查詢所有用戶同時能得到其下所有role。或查詢所有角色同時得到每個角色有哪些用戶
創建角色表

create table role(
	id int PRIMARY key auto_increment,
	roleName varchar(20),
	roleDesc varchar(20)
);

創建中間表

-- 中間表
create table user_role(
	uid int,
	rid int,
	primary key(uid,rid),
	foreign key(uid) REFERENCES user(uid),
	foreign key(rid) REFERENCES role(id)
);

在這裏插入圖片描述

Role.java

public class Role {
    private Integer id;
    private String roleName;
    private String roleDesc;

    //一個角色對應多個用戶
    private List<User> users;

    //省略get/set
}

User.java

public class User {
    private Integer id;
    private String username;
    private String password;
    private String address;
    private String sex;
    private Date birthday;

    //一個用戶多個角色
    private List<Role> roles;

    //省略get/set
}

先從User一方考慮:

UserDao.java

//返回所有user對象 包含用戶對應的角色
public List<User> findAll();

先練習下sql語句

select * from user u,user_role ur where u.uid=ur.uid;
select * from user u left join user_role ur where u.uid=ur.uid;
select * from (user u left join user_role ur on u.uid=ur.uid) left join role r on ur.rid=r.id;
select u.*,r.* from (user u left join user_role ur on u.uid=ur.uid) left join role r on ur.rid=r.id;

UserDao.xml sql

<mapper namespace="cn.ahpu.dao.UserDao">
    <resultMap id="users" type="user">
        <id column="uid" property="id"></id>
        <result column="uname" property="username"></result>
        <result column="password" property="password"></result>
        <result column="sex" property="sex"></result>
        <result column="birthday" property="birthday"></result>
        <result column="address" property="address"></result>
        <!--配置角色集合-->
        <collection property="roles" ofType="role">
            <id column="id" property="id"></id>
            <result column="roleName" property="roleName"></result>
            <result column="roleDesc" property="roleDesc"></result>
        </collection>
    </resultMap>

    <select id="findAll" resultMap="users">
        select u.*,r.* from (user u left join user_role ur on u.uid=ur.uid) left join role r on ur.rid=r.id;
    </select>
</mapper>

TestMybatisMany2Many.java 測試類

/**
     * 查詢所有用戶 包含角色信息
     */
    @Test
    public void testFindAll(){
        //獲取sqlSession對象
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml")).openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> list = userDao.findAll();
        for (User user : list) {
            System.out.println(user);
        }
        //關閉資源
        sqlSession.close();
    }

再從role一方考慮(和前面一樣 雷同)

RoleDao.java 接口方法

//查詢所有角色 包含用戶對象
public List<Role> findAll();

RoleDao.xml sql配置 ★

<mapper namespace="cn.ahpu.dao.RoleDao">
    <resultMap id="roles" type="role">
        <id column="id" property="id"></id>
        <result column="roleName" property="roleName"></result>
        <result column="roleDesc" property="roleDesc"></result>
        <!--配置屬性:用戶集合-->
        <collection property="users" ofType="user">
            <id column="uid" property="id"></id>
            <result column="uname" property="username"></result>
            <result column="password" property="password"></result>
            <result column="sex" property="sex"></result>
            <result column="birthday" property="birthday"></result>
            <result column="address" property="address"></result>
        </collection>
    </resultMap>

    <select id="findAll" resultMap="roles">
        select r.*,u.* from (role r left join user_role ur on r.id=ur.rid) left join user u on ur.uid=u.uid;
    </select>
</mapper>

TestMybatisMany2Many 測試java方法

/**
     * 查詢所有角色 包含用戶信息
     */
    @Test
    public void test2FindAll(){
        //獲取sqlSession對象
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(this.getClass().getClassLoader().getResourceAsStream("mybatis-config.xml")).openSession();
        RoleDao roleDao = sqlSession.getMapper(RoleDao.class);
        List<Role> list = roleDao.findAll();
        for (Role role : list) {
            System.out.println(role);
        }
        //關閉資源
        sqlSession.close();
    }

六、總結

1. 連接池(數據源):
	type: POOLED,UNPOOLED ,JNDI
2. 事務問題
	1) 設置手動提交(開啓事務)
	2) 提交(提交事務)
	3) 回滾事務
	4) 設置自動提交(還原狀態)
     openSession()  -- 不可以自動提交事務的SqlSession對象
     openSession(true)  -- 可以自動提交事務的SqlSession對象
3. 動態sql語句
 	if   多條件查詢
	where   幫助程序員處理第一個and符號,結合if語句使用
	sql片段: 提取常用的代碼
	foreach: 循環
		屬性:collection="list | array", item="循環中每一個對象" open="前綴" close="後綴" seperator="分隔符"
	
4. 關係映射-- 一對一: 一個賬戶對應一個用戶
	1) 聲明一個pojo,包含賬戶和用戶所有的屬性
	2) 在account聲明一個User的屬性
		<result property="user.屬性" column>
	3)在account聲明一個User的屬性
		<association property="屬性名" javaType="類型">
			<result property="屬性" column>	
		</association>
5. 關係映射-- 一對多: 一個用戶對應多個賬戶
	1) 在user類中聲明一個賬戶的集合屬性
	2) <collection property="屬性名" ofType="類型">
			<result property="屬性" column>	
	   </collection>
6. 關係映射-- 多對多:兩個一對多
	關係映射核心:sql語句查詢到所有數據,折騰好返回值類型使查到的數據能一一映射到對象屬性
	
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章