day17 dbutils 和 jdbc 多表操作

Posted Spurs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了day17 dbutils 和 jdbc 多表操作相关的知识,希望对你有一定的参考价值。

Author:相忠良
Email: [email protected]
起始于:June 12, 2018
最后更新日期:June 15, 2018

声明:本笔记依据传智播客方立勋老师 Java Web 的授课视频内容记录而成,中间加入了自己的理解。本笔记目的是强化自己学习所用。若有疏漏或不当之处,请在评论区指出。谢谢。
涉及的图片,文档写完后,一次性更新。

1. dbutils 框架简化 jdbc 开发

commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。因此dbutils成为很多不喜欢hibernate的公司的首选。

API介绍:

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

工具类

  • 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如下:

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 的例子:

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", "[email protected]", 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[] = { "[email protected]", 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 树

无限级分类树,通过树状数据结构设计,建了个数据库表,从而避免了递归。
剩下内容略。












以上是关于day17 dbutils 和 jdbc 多表操作的主要内容,如果未能解决你的问题,请参考以下文章

day14_DBUtils学习笔记

java学习日记(day30--dbutils)

第三十天-加强2-多表查询&JDBC&连接池&DBUtils&综合案例悟空教程

DBUtils和连接池

day17 10.jdbc的crud操作

10.25JDBC之Apache-DBUtils实现CRUD操作