Author:相忠良
Email: ugoood@163.com
起始於:June 12, 2018
最後更新日期:June 15, 2018java
聲明:本筆記依據傳智播客方立勳老師 Java Web 的授課視頻內容記錄而成,中間加入了本身的理解。本筆記目的是強化本身學習所用。如有疏漏或不當之處,請在評論區指出。謝謝。
涉及的圖片,文檔寫完後,一次性更新。mysql
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
本節工程準備:數據庫
mysql-connector-java-5.0.8-bin.jar commons-dbcp-1.2.2.jar commons-pool.jar commons-dbutils-1.2.jar
;dbcpconfig.properties
(該配置文件內容下面有)供建立鏈接池的工具 dbcp 所使用。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); } }
ResultSetHandler 接口的實現類:
準備:
把一下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 應只有增刪改查。下節介紹正常開發中怎樣作轉帳。
先弄個 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!
方立勳老師叨叨了半天,竟說上面的方法不優雅~!!!
他說優雅的解決方式是用spring或 ThreadLocal 類去解決。
ThreadLocal 的使用可使咱們在線程範圍內共享數據。
ThreadLocal 就是一個 key = thread 的 map 容器。
接下來,纔是這夥計講的重點,個人天啊~! (略...)
準備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必須記住多,那就得設計,不然沒必要設計。
例如:訂單必須顯示訂單項,但部門不顯示員工。
級聯刪除有好幾種呢。
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"); }
準備老師和學生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); }
無限級分類樹,經過樹狀數據結構設計,建了個數據庫表,從而避免了遞歸。 剩下內容略。