day17 dbutils 和 jdbc 多表操做

Author:相忠良
Email: ugoood@163.com
起始於:June 12, 2018
最後更新日期:June 15, 2018java

聲明:本筆記依據傳智播客方立勳老師 Java Web 的授課視頻內容記錄而成,中間加入了本身的理解。本筆記目的是強化本身學習所用。如有疏漏或不當之處,請在評論區指出。謝謝。
涉及的圖片,文檔寫完後,一次性更新。mysql

1. dbutils 框架簡化 jdbc 開發

commons-dbutils 是 Apache 組織提供的一個開源 JDBC工具類庫,它是對JDBC的簡單封裝,學習成本極低,而且使用dbutils能極大簡化jdbc編碼的工做量,同時也不會影響程序的性能。所以dbutils成爲不少不喜歡hibernate的公司的首選。web

API介紹:spring

  • org.apache.commons.dbutils.QueryRunner提供了 update(增刪改) 和 query(查詢)
  • org.apache.commons.dbutils.ResultSetHandler

工具類sql

  • org.apache.commons.dbutils.DbUtils

本節工程準備:數據庫

  1. 創建 day17 java 工程;
  2. 創建 lib,導入 jar 包並變奶瓶:mysql-connector-java-5.0.8-bin.jar commons-dbcp-1.2.2.jar commons-pool.jar commons-dbutils-1.2.jar
  3. src下創建dbcpconfig.properties(該配置文件內容下面有)供建立鏈接池的工具 dbcp 所使用。
  4. 建立cn.wk.utils.JdbcUtils用來建立鏈接池。

dbcpconfig.properties以下:apache

driverClassName=com.mysql.jdbc.Driver
url=jdbc\:mysql\://localhost\:3306/day17
username=root
password=root

initialSize=10
maxActive=50
maxIdle=20
minIdle=5
maxWait=60000
connectionProperties=useUnicode=true;characterEncoding=utf8
defaultAutoCommit=true
defaultReadOnly=
defaultTransactionIsolation=READ_COMMITTED

cn.wk.utils.JdbcUtils建立鏈接池的工具類以下:數組

package cn.wk.utils;
import java.io.InputStream;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;

public class JdbcUtils {
    // 建立鏈接池
    private static DataSource ds;
    static {
        try {
            Properties prop = new Properties();
            InputStream in = JdbcUtils.class.getClassLoader()
                    .getResourceAsStream("dbcpconfig.properties");
            prop.load(in);

            BasicDataSourceFactory factory = new BasicDataSourceFactory();
            ds = factory.createDataSource(prop);
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public static DataSource getDataSource(){return ds;}
    // dbutils 框架會自動幫咱們釋放連接,因此不用寫 release 方法
}

day17工程到此已徹底準備穩當,開始下面的實驗啦。
爲模擬將數據庫取出的user數據封裝到bean中,因此先建一個cn.wk.domain.User,以下:數據結構

package cn.wk.domain;
import java.util.Date;

public class User {
    private int id;
    private String name;
    private String password;
    private String email;
    private Date birthday;
  // 後面的getter和setter方法省略
}

用 dbutils 完成 crud 的例子:mvc

package cn.wk.dbutils.demo;

import java.sql.SQLException;
import java.util.Date;
import java.util.List;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.junit.Test;

import cn.wk.domain.User;
import cn.wk.utils.JdbcUtils;

public class Demo1 {

    /*
      create database day17;
      use day17;
      create table users(
        id int primary key,
        name varchar(40),
        password varchar(40),
        email varchar(60),
        birthday date
      );
     */

    // 使用 dbtuils 完成數據庫的 crud

    @Test
    public void insert() throws SQLException {
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "insert into users(id,name,password,email,birthday) values(?,?,?,?,?)";
        Object params[] = { 2, "bbb", "123", "aa@gmail.com", new Date() };
        runner.update(sql, params);
    }

    @Test
    public void update() throws SQLException {
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "update users set email=? where id=?";
        Object params[] = { "aaaaaa@163.com", 1 };
        runner.update(sql, params);
    }

    @Test
    public void delete() throws SQLException {
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "delete from users where id=?";
        runner.update(sql, 1);
    }

    @Test
    public void find() throws SQLException {
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "select * from users where id=?";
        User user = (User) runner.query(sql, 1, new BeanHandler(User.class));
        System.out.println(user.getEmail());
    }

    @Test
    public void getAll() throws SQLException {
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "select * from users";
        List list = (List) runner.query(sql, new BeanListHandler(User.class));
        System.out.println(list);
    }

    @Test
    // 用 dbutils 做批處理
    public void batch() throws SQLException {
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "insert into users(id,name,password,email,birthday) values(?,?,?,?,?)";
        Object params[][] = new Object[3][5];
        for (int i = 0; i < params.length; i++) { // 3條記錄
            params[i] = new Object[] { i + 1, "aa" + i, "123", i + "@sina.com",
                    new Date() };
        }
        runner.batch(sql, params);
    }
}

1.1 ResultSetHandler 接口的實現類

ResultSetHandler 接口的實現類:

  • ArrayHandler:把結果集中的第一行數據轉成對象數組;
  • ArrayListHandler:把結果集中的每一行數據都轉成一個數組,再存放到List中;
  • BeanHandler:將結果集中的第一行數據封裝到一個對應的JavaBean實例中;
  • BeanListHandler:將結果集中的每一行數據都封裝到一個對應的JavaBean實例中,存放到List裏;
  • ColumnListHandler:將結果集中某一列的數據存放到List中;
  • KeyedHandler(name):將結果集中的每一行數據都封裝到一個Map裏,再把這些map再存到一個map裏,其key爲指定的key;
  • MapHandler:將結果集中的第一行數據封裝到一個Map裏,key是列名,value就是對應的值;
  • MapListHandler:將結果集中的每一行數據都封裝到一個Map裏,而後再存放到List;
  • ScalarHandler:將結果集的某列轉成一個對象(標量)。

2. 用 dbutils 進行事務管理

準備:
把一下sql語句在day17數據庫中執行。

create table account(
    id int primary key auto_increment,
    name varchar(40),
    money float
)character set utf8 collate utf8_general_ci;

insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);

使用 dbutils 管理事務的完整案例代碼:

JdbcUtils 增長了 getConnection() 方法,以下:

public class JdbcUtils {
    // 建立鏈接池
    private static DataSource ds;
    static {
        try {
            Properties prop = new Properties();
            InputStream in = JdbcUtils.class.getClassLoader()
                    .getResourceAsStream("dbcpconfig.properties");
            prop.load(in);

            BasicDataSourceFactory factory = new BasicDataSourceFactory();
            ds = factory.createDataSource(prop);
        } catch (Exception e) {throw new ExceptionInInitializerError(e);}
    }

    public static DataSource getDataSource() {
        return ds;
    }

    // dbutils 框架會自動幫咱們釋放連接,因此不用寫 release 方法

    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
}

dbutils 管理事務例子:

public class AccountDao {
    // 從 a-->b帳戶 轉100元
    public void transfer() throws SQLException {

        Connection conn = null;
        try {
            conn = JdbcUtils.getConnection();
            conn.setAutoCommit(false); // 設置開啓事務

            QueryRunner runner = new QueryRunner();

            String sql1 = "update account set money=money-100 where name='aaa'";
            runner.update(conn, sql1); // 用開啓了事務的 conn 去執行 sql

            String sql2 = "update account set money=money+100 where name='bbb'";
            runner.update(conn, sql2);

            conn.commit();
        } finally {if (conn != null) conn.close();}
    }
}

但 dao 層不該有上述這樣的 transfer 方法(包含了業務邏輯,違背了mvc),dao 應只有增刪改查。下節介紹正常開發中怎樣作轉帳。

2.1 正常開發中的轉帳實現

先弄個 Account 的 bean:

package cn.wk.domain;
public class Account {
    private int id;
    private String name;
    private double money;
// getter,setter省略
}

AccountDao 加上下面2方法:

public class AccountDao {
    public AccountDao() {super();}

    private Connection conn;

    public AccountDao(Connection conn) { // 由外界提供統一的一個 conn
        this.conn = conn;
    }

    public void update(Account a) {
        try {
            QueryRunner runner = new QueryRunner();
            String sql = "update account set money=? where id=?";
            Object params[] = { a.getMoney(), a.getId() };
            runner.update(conn, sql, params);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Account find(int id) {
        try {
            QueryRunner runner = new QueryRunner();
            String sql = "select * from account where id=?";
            return (Account) runner.query(conn, sql, id, new BeanHandler(
                    Account.class));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

cn.wk.service.BusinessService對 web 層提供轉帳服務:

public class BusinessService {

    @Test
    public void test() throws SQLException {
        transfer(1, 2, 100);
    }

    public void transfer(int sourceid, int targetid, double money)
            throws SQLException {

        Connection conn = null;
        try {
            conn = JdbcUtils.getConnection();
            conn.setAutoCommit(false);      // 開啓事務

            AccountDao dao = new AccountDao(conn); // <-- 把 conn 傳進去啦

            Account a = dao.find(sourceid); // select
            Account b = dao.find(targetid); // select

            a.setMoney(a.getMoney() - money);
            b.setMoney(b.getMoney() + money);

            dao.update(a);                  // update
//          int aa = 1/0;
            dao.update(b);                  // update 得做爲總體執行

            conn.commit();                  // 提交事務
        } finally {if (conn != null) conn.close();}
    }
}

service 層只有這樣寫,才保證轉帳操做用的是同一個 connection 並用事務去完成!
同時注意到 dao 層已經被改造,dao 層經過有參構造函數,傳入了一個 service 層提供的一個已經開啓了事務的 connection!

2.2 ThreadLocal - 線程範圍內共享數據

方立勳老師叨叨了半天,竟說上面的方法不優雅~!!!
他說優雅的解決方式是用spring或 ThreadLocal 類去解決。

ThreadLocal 的使用可使咱們在線程範圍內共享數據。

ThreadLocal 就是一個 key = thread 的 map 容器。

接下來,纔是這夥計講的重點,個人天啊~! (略...)

3. jdbc 多表操做(1:n)

準備2個bean,以下:

package cn.wk.domain;
public class Department {
    private String id;
    private String name;
    private Set employees = new HashSet(); //看是否有顯示需求,若無則刪除這個
// getter setter...
}
package cn.wk.domain;
public class Employee {
    private String id;
    private String name;
    private double salary;
    private String department_id;
  // getter setter...
}

數據庫建立2表:

use day17;
create table department(
  id varchar(40) primary key,
  name varchar(40)
);

create table employee(
  id varchar(40) primary key,
  name varchar(40),
  salary double,
  department_id varchar(40),
  constraint department_id_FK foreign key(department_id) references department(id)
);

多表查詢的一個dao:

public class DepartmentDao {

    public void add(Department d) throws SQLException {
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());

        // 1. 把 department 對象插入 department 表
        String sql = "insert into department(id,name) values(?,?)";
        Object params[] = { d.getId(), d.getName() };
        runner.update(sql, params);

        // 2. 把department 對象中的員工們插入到 employee 表
        Set<Employee> set = d.getEmployees();
        for (Employee e : set) {
            sql = "insert into employee(id,name,salary,department_id) values(?,?,?,?)";
            params = new Object[] { e.getId(), e.getName(), e.getSalary(),
                    d.getId() };
            runner.update(sql, params);
        }
    }

    public Department find(String id) throws SQLException{      
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());

        //1.找部門表,查出部門的基本信息
        String sql = "select * from department where id=?";
        Department d = (Department) runner.query(sql, id, new BeanHandler(Department.class));

        //2.找員工表,找出部門下面全部員工
        sql = "select * from employee where department_id=?";
        List list = (List) runner.query(sql, id, new BeanListHandler(Employee.class));      

        d.getEmployees().addAll(list);      
        return d;
    }
}

多表查詢的測試:

public class BService {
    @Test
    public void add() throws SQLException{      

        Department d = new Department();
        d.setId("1");
        d.setName("開發部");

        Employee e1 = new Employee();
        e1.setId("1");
        e1.setName("aa");
        e1.setSalary(10000);

        Employee e2 = new Employee();
        e2.setId("2");
        e2.setName("bb");
        e2.setSalary(10000);


        d.getEmployees().add(e1);
        d.getEmployees().add(e2);

        DepartmentDao dao = new DepartmentDao();
        dao.add(d);
    }

    @Test
    public void find() throws SQLException{
        DepartmentDao dao  = new DepartmentDao();
        Department d = dao.find("1");
        System.out.println(d);
    }
}

方立勳說:1對多能不用就不用。由於若1中記多,太多了內存崩。
需求讓你1必須記住多,那就得設計,不然沒必要設計。
例如:訂單必須顯示訂單項,但部門不顯示員工。

3.1 jdbc 多表操做 - 級聯

級聯刪除有好幾種呢。

  • 刪部門表記錄,對應員工部門號清空 on delete set null;
  • 刪部門表記錄,也刪除對應員工 on delete cascade;
  • 還有,用時再查。
create table employee(
  id varchar(40) primary key,
  name varchar(40),
  salary double,
  department_id varchar(40),
  constraint department_id_FK foreign key(department_id) references department(id)  
);

// 修改員工表的外鍵約束

alter table employee drop foreign key department_id_FK;
alter table employee add constraint department_id_FK foreign key(department_id) references department(id) on delete set null;

dao的刪除方法

public void delete(String id) throws SQLException{
  QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
  String sql = "delete from department where id=?";
  runner.update(sql, id);
}

級聯刪除測試:

@Test
public void delete() throws SQLException{
  DepartmentDao dao  = new DepartmentDao();
  dao.delete("1");
}

4. jdbc 多表操做(n:m)

準備老師和學生bean和表,他們是 n:m 的關係。

public class Student {
    private String id;
    private String name;
    private Set teachers = new HashSet();
  // getter setter...
}
public class Teacher {
    private String id;
    private String name;
    private double salary;
    private Set students = new HashSet();
  // getter setter...
}

數據庫生3表:

use day17;
create table teacher(
  id varchar(40) primary key,
  name varchar(40),
  salary double
);

create table student(
  id varchar(40) primary key,
  name varchar(40)
);

create table teacher_student(
  teacher_id varchar(40),
  student_id varchar(40),
  primary key(teacher_id,student_id),
  constraint teacher_id_FK foreign key(teacher_id) references teacher(id),
  constraint student_id_FK foreign key(student_id) references student(id)
);

操做多表dao:

public class TeacherDao {

public void add(Teacher t) throws SQLException {

        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());

        //1`.取出老師存老師表
        String sql = "insert into teacher(id,name,salary) values(?,?,?)";
        Object params[] = {t.getId(),t.getName(),t.getSalary()};
        runner.update(sql, params);


        //2.取出老師全部學生的數據,存學生表
        Set<Student> set = t.getStudents();
        for(Student s : set){
            sql = "insert into student(id,name) values(?,?)";
            params = new Object[]{s.getId(),s.getName()};
            runner.update(sql, params);

            //3.更新中間表,說明老師和學生的關係
            sql = "insert into teacher_student(teacher_id,student_id) values(?,?)";
            params = new Object[]{t.getId(),s.getId()};
            runner.update(sql, params);
        }
    }

    public Teacher find(String id) throws SQLException{

        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());

        //1.找老師表,找出老師的基本信息
        String sql = "select * from teacher where id=?";
        Teacher t = (Teacher) runner.query(sql, id, new BeanHandler(Teacher.class));

        //2.找出老師的全部學生
        sql = "select s.* from teacher_student ts,student s where ts.teacher_id=? and ts.student_id=s.id";
        List list = (List) runner.query(sql, id, new BeanListHandler(Student.class));

        t.getStudents().addAll(list);
        return t;
    }

    public void delete(String id){
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "delete from teacher where id=?";
        // 未完成
    }
}

測試:

@Test
public void addTeacher() throws SQLException {
  Teacher t = new Teacher();
  t.setId("1");
  t.setName("二麻子");
  t.setSalary(100000);

  Student s1 = new Student();
  s1.setId("1");
  s1.setName("aa");

  Student s2 = new Student();
  s2.setId("2");
  s2.setName("bb");

  t.getStudents().add(s1);
  t.getStudents().add(s2);

  TeacherDao dao = new TeacherDao();
  dao.add(t);
}

@Test
public void findTeacher() throws SQLException{
  TeacherDao dao = new TeacherDao();
  Teacher t = dao.find("1");
  System.out.println(t);
}

5. web 樹

無限級分類樹,經過樹狀數據結構設計,建了個數據庫表,從而避免了遞歸。 剩下內容略。

相關文章
相關標籤/搜索