1. 程式人生 > >dbutils 的使用,事務處理,操作多表

dbutils 的使用,事務處理,操作多表

dbutils 的使用,事務處理,
多表操作,
oracle 大資料處理
作者:呂鵬
時間:2011-08-08
首先還是簡單的回顧一下昨天我們講的什麼, 我們昨天講了資料庫連線池, 為防止頻繁訪問
資料庫而建立的連線池的實現有兩種一個是自定義連線池, 使用動態代理方式, 另外一個是
使用 DBCP,C3P0,Tomcat 等伺服器自帶的,都可以實現連線池。下午講了手動編寫自己的
JDBC 框架,簡化了 CRUD 操作,為我們今天講這個 DBUtils 打下一個基礎。
目錄:
一、DBUtils 框架的使用
1、使用 dbutils 做增刪改查,批處理以及大文字操作
2、使用 DBUtils 框架管理事務。 (模擬銀行轉賬)
二、使用 JDBC 操作多個表
1、一對多(部門與員工)
2、多對多(學生和老師 學生與課程)
3、一對一(人和身份證)
三、關於 Oracle 資料對於二進位制資料的存取(面試題)
一、DBUitls 框架的使用
1、使用 dbutils 做增刪改查,批處理以及大文字操作
package cn.itcast.dbutils.demo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.util.List;
import javax.sql.rowset.serial.SerialBlob;
import javax.sql.rowset.serial.SerialClob;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.junit.BeforeClass;
import org.junit.Test;
import cn.itcast.dbutil.JDBCUtils;
/**
* 使用dbutils做增刪改查 批處理大文字操作
* @author 呂鵬
*
*/
public class Demo01 {
/**建立demo表
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| name | varchar(10) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
*/
static QueryRunner runner;
@BeforeClass
public static void beforeClass(){
//獲取一個QueryRunner物件(構造方法帶資料來源 自動完成連線建立和釋放)
runner = new QueryRunner(JDBCUtils.getDataSource());
}
/**
* 執行插入操作
*/
@Test
public void testInsert()throws Exception{
String sql = "insert into demo values(?,?)";//宣告sql
Object[] params = {2,"insert"};//初始化引數
runner.update(sql, params);
}
/**
* 執行刪除操作
* @throws Exception
*/
@Test
public void testDelete() throws Exception{
String sql = "delete from demo where id = ?";
runner.update(sql, 1);
}
/**
* 執行更新操作
* @throws Exception
*/
@Test
public void testUpdate() throws Exception{
String sql = "update demo set name=? where id=?";
Object[] params = {"update",1};
runner.update(sql, params);
}
/**
* 查詢列表操作
* @throws Exception
*/
@Test
public void testListQuery() throws Exception{
String sql = "select * from demo";
List<Demo> list = (List<Demo>) runner.query(sql, new
BeanListHandler(Demo.class));
for(int i=0;i<list.size();i++){
System.out.println("ID:"+list.get(i).getId()+" 姓名是:"+list.get(i).getName());
}
}
/**
* 查詢物件操作
* @throws Exception
*/
@Test
public void testObjectQuery() throws Exception{
String sql = "select * from demo where id = ?";
Demo demo = (Demo) runner.query(sql, 1, new BeanHandler(Demo.class));
System.out.println("ID:"+demo.getId()+" Name:"+demo.getName());
}
/**
* 批處理操作
* @throws Exception
*/
@Test
public void testBatch() throws Exception{
String sql = "insert into demo value (?,?)";
Object[][] params = new Object[10][];
for(int i=0;i<10;i++){
params[i] = new Object[]{i,"batch"};
}
runner.batch(sql, params);
}
/**
* 大文字操作
* @throws Exception
*/
@Test
public void testClob() throws Exception{
String sql = "insert into clob values(?)";
File file = new File("c:/a.txt");
Long l = file.length();
char[] buffer = new char[l.intValue()];
FileReader reader = new FileReader(file);
reader.read(buffer);
SerialClob clob = new SerialClob(buffer);
runner.update(sql, clob);
}
/**
* 二進位制影象操作
* @throws Exception
*/
@Test
public void testBlob() throws Exception{
String sql = "insert into blob values(?)";
File file = new File("c:/a.jpg");
Long l = file.length();
byte[] buffer = new byte[l.intValue()];
FileInputStream input = new FileInputStream(file);
input.read(buffer);
SerialBlob blob = new SerialBlob(buffer);
runner.update(sql,blob);
}
}
以上程式碼是使用 DButils 框架完成的增刪改差批處理以及大文字操作,其中,使用 了
QueryRunner 這個類,這個類是專門負責處理 sql 語句的,有四個構造方法,我們經常使用
到的是一個無參的構造方法和一個有參的, 引數就是資料來源, 當我們給其提供資料來源的時候
就是讓框架為我們自動的建立資料庫連線, 並釋放連線, 當這是處理一般操作的時候, 當我
們要進行事務處理的時候, 那麼連線的釋放就要由我們自己來決定了, 所以我們就不再使用
帶引數的 QueryRunner 構造方法了。具體用法下面詳解。
2、使用 DBUtils 框架管理事務。
剛才我們的增刪改差是為了演示我們的例子, 所以我們忽略了異常也沒有使用事務, 現在我
們使用事務模擬一個銀行轉賬,看一下 DBUtils 框架是如何幫助我們完成事務管理的。
在講解事務之前,需要讓大家理解一件事情就是,我們使用事務就不能使用其 QueryRunner
的有參構造方法了, 因為我們要自己定義事務的開關, 如果使用帶引數的構造就要讓框架幫
助我們關閉資料庫連線了, 所以這裡首先大家要明白。 那麼我們如何將我們的連線傳遞給持
久層呢,有同學說使用構造方法,是挺好的,通過構造方法將我們的 conn 傳遞給持久層,
但是為了降低耦合度, 我們不想在業務層出現持久層的程式碼, 那我們就使用一個類幫助我們
把這個連線傳遞過去, 就相當於使用工廠幫我們生成持久層物件一樣, 那我們使用什麼類呢,
我們使用 ThreadLoacl 這個類
關於 ThreadLocal 這個類, 可以查閱幫助文件瞭解, 在這裡我簡單的說明一下它的作用,
它的作用是在一個執行緒當中記錄我們的變數,這個變數可以是任意變數,包括我們的連線,
就是說我們生成一個連線以後可以放在這個執行緒中, 這樣, 只要是這個執行緒中任何物件都可
以共享這個連線,當這個執行緒結束以後,執行緒要刪除這個連線。
具體使用方法:
//宣告執行緒共享變數
public static ThreadLocal<Connection> container = new ThreadLocal<Connection>();
//獲取共享變數
public static ThreadLocal<Connection> getContainer(){
return container;
}
這樣,那麼我們就可以幫有關事務的操作,這些操作原本應該在業務層出現的程式碼, 現
在我們都把它放在工具類中, 因為不管是事務開啟還是事務提交, 回滾, 都是和連線關聯的,
我們把連線放在了這個共享的執行緒當中,那麼其方法也是共享的:工具類如下:
package cn.itcast.dbutil;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class JDBCUtils {
//c3p0連線池
public static ComboPooledDataSource ds = new ComboPooledDataSource();
//宣告執行緒共享變數
public static ThreadLocal<Connection> container = new ThreadLocal<Connection>();
//獲取共享變數
public static ThreadLocal<Connection> getContainer(){
return container;
}
/**
* 獲取資料來源
*/
public static DataSource getDataSource(){
return ds;
}
/**
* 獲取當前執行緒上的連線 開啟事務
*/
public static void startTransaction(){
Connection conn = container.get();//首先獲取當前執行緒的連線
if(conn == null){//如果連線為空
conn = getConnection(); //從連線池中獲取連線
container.set(conn);//將此連線放在當前執行緒上
}
try {
conn.setAutoCommit(false);//開啟事務
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(),e);
}
}
/**
* 提交事務
*/
public static void commit() {
Connection conn = container.get();// 從當前執行緒上獲取連線
if (conn != null) {// 如果連線為空,則不做處理
try {
conn.commit();// 提交事務
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
/**
*回滾事務
*/
public static void rollback(){
Connection conn = container.get();//檢查當前執行緒是否存在連線
if(conn != null){
try {
conn.rollback();//回滾事務
// container.remove();//如果回滾了,就移除這個連線
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(),e);
}
}
}
/**
* 關閉連線
*/
public static void close(){
Connection conn = container.get();
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(),e);
}finally{
container.remove();//從當前執行緒移除連線 切記
}
}
}
/**
* 獲取資料庫連線
* @return 資料庫連線 連線方式(連線池 錯c3p0)
*/
public static Connection getConnection(){
try {
return ds.getConnection();
} catch (Exception e) {
throw new RuntimeException();
}
}
}
在這個工具類裡要特別注意, 在關閉連線方法的裡面, finally 裡需要將這個執行緒的連線
移除,不然這個執行緒的連線得不到釋放。
這個明白了以後再看我們的銀行轉賬的模擬程式:
首先我們在模擬的 DAO 中完成查詢賬戶和修改賬戶的方法:
(1)查詢賬戶和修改賬戶程式碼:
/**
* 模擬DAO 查詢賬戶 更新賬戶
* @author 呂鵬
*
*/
class AccountDAO{
//宣告連線
private Connection conn;
public AccountDAO(){
this.conn = JDBCUtils.getContainer().get();//執行緒共享物件獲取連線
}
/*8
* 根據ID查詢賬戶
*/
public Account findAccount(int id) {
//處理事務 ,無參構造
QueryRunner runner = new QueryRunner();
String sql = "select * from account where id=?";
Object[] params={id};
try {
//附加連線 處理者為BeanHandler
return (Account) runner.query(conn, sql, params, new
BeanHandler(Account.class));
} catch (Exception e) {
System.out.println("eeeeeeeeeeeeeee");
throw new RuntimeException(e.getMessage(),e);
}
}
/**
* 更新賬戶
* @param a
*/
public void updateAccount(Account a){
QueryRunner runner = new QueryRunner();
String sql = "update account set money=? where id=?";
Object[] params = {a.getMoney(),a.getId()};
try {
runner.update(conn, sql, params);
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(),e);
}
}
}
(2)完成業務邏輯層的轉賬事務
class AccountService{
/**
* 轉賬方法
* @param fromID 原始賬戶
* @param toID 目標賬戶
* @param money 轉入多少錢
*/
public void trafferAccount(int fromID,int toID,float money){
try{
JDBCUtils.startTransaction(); //開啟事務
AccountDAO dao = Factory.getInstance().getAccountDAO();//注意這行程式碼一
定要放在開啟事務之後 ,連線才會被建立
Account from = dao.findAccount(fromID);
Account to = dao.findAccount(toID);
from.setMoney(from.getMoney()-money);
to.setMoney(to.getMoney()+money);
dao.updateAccount(from);
dao.updateAccount(to);
JDBCUtils.commit(); //提交事務
System.out.println("轉賬成功");
}catch (Exception e) {
System.out.println("轉賬失敗");
JDBCUtils.rollback();//回滾事務
throw new RuntimeException();
}finally{
JDBCUtils.close();
}
}
}
被標註紅色的程式碼要注意, 其重點不只是工廠模式的使用, 更重要的一點要明白, 我們的持
久層的連線是在其構造方法中獲取的, 如果我們把這個程式碼放在前面的話, 就提前構造了其
物件, 但是我們的連線卻是在事務開啟的時候才建立的, 如此我們的連線就無法傳遞到持久
層當中了。 所以這段程式碼要放在之後。 待我們的連線被放置在當前執行緒以後再去呼叫工廠構
造我們的物件。
(3)靜態工廠獲取例項
/**
* 靜態工廠 獲取操作物件
* @author 呂鵬
*
*/
class Factory{
//私有靜態工廠例項
private static Factory instance = null;
//私有構造
private Factory() {
}
/**
* 提供一個靜態公共方法獲取工廠例項
* @return
*/
public static Factory getInstance(){
if (instance == null) {
synchronized (Factory.class) {
if (instance == null) {
instance = new Factory();
}
}
}
return instance;
}
/**
* 獲取操作物件 簡化程式碼不讀配置檔案了
* @return
*/
public AccountDAO getAccountDAO(){
return new AccountDAO();
}
public AccountService getAccountService(){
return new AccountService();
}
}
(4)測試類:
/**
* 轉賬測試類
* @author 呂鵬
*
*/
public class Test {
public static void main(String[] args) {
//使用工廠模式生成業務層物件
AccountService service = Factory.getInstance().getAccountService();
//呼叫業務層的方法 從1號賬戶轉賬100到2號賬戶
service.trafferAccount(1, 2, 100);
}
}
效果:
mysql> select * from account;
+------+------+-------+
| id | name | money |
+------+------+-------+
| 1 | tom | 300 |
| 2 | cc | 1700 |
+------+------+-------+
2 rows in set (0.00 sec)
轉賬成功。
從這個模擬的銀行轉賬我們學到了:
(1)在業務層完成事務的處理,持久層只負責操作資料庫
(1)DBUtils 對事務的操作流程
(2)使用 ThreadLoacl 共享單執行緒資訊
二、使用 JDBC 操作多個表
學習使用 JDBC 操作多個表是學習 Hibernate 的基礎,掌握了 JDBC 對多表的操作其實就是
在學習 Hibernate 實現多表操作的底層原理。下面我們來看一下,使用 JDBC 如何完成對多
表的操作,學習多表操作之前我們需要明白什麼叫多表操作:
多表操作其實就是對 多個物件的關係的操作, 如果對帶有關係的物件進行資料庫操作就是
對操作操作。
那物件和物件之間又有什麼關係呢?
(1)一對多
(2)多對多
(3)一對一
。 。 。 。
1、一對多關係操作
我們舉部門和員工的例子, 部門和員工就是典型的一對多的範例, 那麼我們如何描述他們之
前的關係呢?要使用到外來鍵,首先我們來看他們的表如何設計:
package cn.itcast.dao;
import java.util.Set;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import cn.itcast.dbutils.JDBCUtils;
import cn.itcast.domain.Department;
import cn.itcast.domain.Employee;
public class DepertmentDAO {
/**
* 增加一個部門 同時增加其員工
* @param depart
*/
public void addDepartment(Department depart){
try{
QueryRunner run = new QueryRunner(JDBCUtils.getDataSource());
String sql = "insert into department value(?,?,?)";
Object[] params = {1,"開發部"};
run.update(sql, params); //執行插入一個部門
Set<Employee> ems = depart.getEmployees(); //查詢出部門物件的員工集合
for(Employee e : ems){
String sql2 = "insert into employee value(?,?,?) where depart_id = ?";
Object[] params2 = {e.getId(),e.getName(),e.getMoney(),depart.getId()};
run.update(sql2, params2); //執行插入多個員工
}
}catch(Exception e){
throw new RuntimeException();
}
}
/**
* 查詢一個部門,同時查詢其部門下的員工
*/
public Department findDepartment(int id){
Department depart = null;
try{
//首先查詢部門
QueryRunner runner = new QueryRunner(JDBCUtils.getDataSource());
String sql = "select * from department where id = ?";
depart = (Department) runner.query(sql, id, new BeanHandler(Department.class));
//根據部門的ID也就是員工的外來鍵查詢員工集合 設定到depart的屬性當中去
String sql2 = "select * from employee where depart_id = ?";
Set<Employee> emps = (Set<Employee>) runner.query(sql2, id, new
BeanListHandler(Employee.class));
depart.setEmployees(emps);
}catch (Exception e) {
throw new RuntimeException(e.getMessage(),e);
}
return depart;
}
}
2、多對多的關係
多對多是比較複雜的, 在資料庫中我們需要三張表來維護他們的關係, 一張中間表, 但實體
物件只有兩個, 拿學生和課程來講, 一個學生可以選擇多門課程而, 而一個課程又可以被多
個學生選擇,如果維護他們的關係呢,我們看例子:
package cn.itcast.dao;
import java.util.Set;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import cn.itcast.dbutils.JDBCUtils;
import cn.itcast.domain.Course;
import cn.itcast.domain.Student;
public class StudentDAO {
/**
* 新增一個學生,同時新增其選課課程
*/
public void addStudent(Student student){
try{
QueryRunner run = new QueryRunner(JDBCUtils.getDataSource());
String sql1 = "insert into student values(?,?)";
Object params[] = {student.getId(),student.getName()};
Set<Course> courses = student.getCourses();
for(Course c : courses){
String sql2="insert into course values(?,?)";
Object[] params2 = {c.getId(),c.getName()};
run.update(sql2, params2);
String sql3="insert into student_course value(?,?)";
Object[] params3={student.getId(),c.getId()};
run.update(sql3, params3);
}
}catch(Exception e){
throw new RuntimeException(e.getMessage(),e);
}
}
/**
* 查詢一個學生,同時將其選課資訊查詢出來
*
*/
public Student findStudent(int id){
Student student = null;
try {
QueryRunner runner = new QueryRunner(JDBCUtils.getDataSource());
String sql = "select * from student where id = ?";
student = (Student) runner.query(sql, id, new BeanHandler(Student.class));
//這句話什麼意思呢?就是說我們要查詢學生的課程,要保證兩個條件
//(1)保證學生的ID和中間表中的學生ID是一樣的才能保證你要找的是這個學

//(2)保證課程的ID和中間表中的課程ID是一樣的才能保證你要找的是這些課

//兩者缺一不可,只有同時保證這個兩個條件才能確認這些課程就是這個學生
的。
String sql2 = "select * from student_course sc,course c where sc.sid=? and sc.cid=
c.id";
Set<Course> courses = (Set<Course>) runner.query(sql2, id, new
BeanListHandler(Course.class));
student.setCourses(courses);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(),e);
}
return student;
}
}
(3)一對一的關係
身份證和人的關係不就是一對一嗎?
package cn.itcast.dao;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import cn.itcast.dbutils.JDBCUtils;
import cn.itcast.domain.Card;
import cn.itcast.domain.Person;
public class PersonDAO {
/**
* 增加一個人,同時增加一個身份證號碼
* @param person
*/
public void addPerson(Person person){
try{
QueryRunner run = new QueryRunner(JDBCUtils.getDataSource());
String sql1 = "insert into person values(?,?)";
Object[] params = {person.getId(),person.getName()};
run.update(sql1, params);
Card card = person.getCard();
String sql2 = "insert into card values(?,?)";
Object[] params2 = {card.getId(),card.getCard_id()};
run.update(sql2, params2);
}catch(Exception e){
throw new RuntimeException(e.getMessage(),e);
}
}
/**
* 查詢一個人,同時查詢這個人的身份證號碼
*/
public Person findPerson(int id){
Person person = null;
try {
QueryRunner runner = new QueryRunner(JDBCUtils.getDataSource());
String sql = "select * from person where id = ?";
person = (Person) runner.query(sql, id, new BeanHandler(Person.class));
String sql2 = "select * from card where person_id = ?";
Card card = (Card) runner.query(sql2, id, new BeanHandler(Card.class));
person.setCard(card);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(),e);
}
return person;
}
}
注意事項:
不管 java 的物件存在何種關係, 反映到關係型資料庫中, 都是使用外來鍵表示紀錄 (即物件)
的關聯關係。
設計 java 物件如涉及到多個物件相互引用,要儘量避免使用一對多,或多對多關係,而應
使用多對一描述物件之間的關係(或使用延遲載入的方式)。
三、Oracle 中大資料的處理
注意:面試題,如何在 Oracle 資料庫中儲存圖片(二進位制資料) ?
Oracle 定義了一個 BLOB 欄位用於儲存二進位制資料,但這個欄位並不能存放真正的二進位制
資料,只能向這個欄位存一個指標,然後把資料放到指標所指向的 Oracle 的 LOB 段中,
LOB 段是在資料庫內部表的一部分。
因而在操作 Oracle 的 Blob 之前,必須獲得指標(定位器)才能進行 Blob 資料的讀取和寫
入。
如何獲得表中的 Blob 指標呢? 可以先使用 insert 語句向表中插入一個空的 blob(呼叫
oracle 的函式 empty_blob() ) ,這將建立一個 blob 的指標,然後再把這個 empty 的 blob
的指標查詢出來,這樣就可得到 BLOB 物件,從而讀寫 blob 資料了。
1、插入空 blob
insert into test(id,image) values(?,empty_blob());
2、獲得 blob 的 cursor
select image from test where id= ? for update;
Blob b = rs.getBlob(“image”);
注意: 須加 for update,鎖定該行,直至該行被修改完畢,保證不產生併發衝突。
3、利用 io,和獲取到的 cursor 往資料庫讀寫資料
注意:以上操作需開啟事務。
package cn.itcast.oracle;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import org.junit.Test;
import oracle.sql.BLOB;
public class TestOracleBLOB {
//將圖片存入Oracle資料庫
@Test
public void tesetInsert() throws Exception{
Class.forName("oracle.jdbc.OracleDriver");
String url = "jdbc:oracle:thin:@localhost:1521:orcl";
String user = "scott";
String password = "tiger";
Connection conn = DriverManager.getConnection(url, user, password);
//開啟事務
conn.setAutoCommit(false);
// 插入空指標
String sql = "insert into testblob values(?,empty_blob())";
PreparedStatement pst = conn.prepareStatement(sql);
pst.setInt(1, 1);
//執行
pst.executeUpdate();
//查詢該空指標
sql = "select image from testblob where bid=1";
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);
if(rs.next()){
BLOB blob = (BLOB) rs.getBlob(1);
//得到輸出流
OutputStream out = blob.getBinaryOutputStream();
//建立輸入流
File file = new File("c:\\Sunset.jpg");
FileInputStream in = new FileInputStream(file);
byte[] buffer = new byte[1024];
while(in.read(buffer)>0){
out.write(buffer);
}
out.flush();
out.close();
in.close();
}
//提交
conn.commit();
//釋放資源
rs.close();
st.close();
pst.close();
conn.close();
System.out.println("完成");
}
//取出Oracle資料庫中的照片
@Test
public void testFindImage() throws Exception{
Class.forName("oracle.jdbc.OracleDriver");
String url = "jdbc:oracle:thin:@localhost:1521:orcl";
String user = "scott";
String password = "tiger";
Connection conn = DriverManager.getConnection(url, user, password);
String sql = "select image from testblob where bid=1";
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);
if(rs.next()){
BLOB blob = (BLOB) rs.getBlob(1);
//輸入流
InputStream in = blob.getBinaryStream();
FileOutputStream out = new FileOutputStream("c:\\1234.jpg");
byte[] buffer = new byte[1024];
while(in.read(buffer)>0){
out.write(buffer);
}
out.flush();
out.close();
in.close();
}
rs.close();
st.close();
conn.close();
}
}