1.資料庫連線池
什麼是資料庫連線池
簡單來說:資料庫連線池就是提供連線的。。。
為什麼我們要使用資料庫連線池
- 資料庫的連線的建立和關閉是非常消耗資源的
- 頻繁地開啟、關閉連線造成系統效能低下
編寫連線池
- 編寫連線池需實現java.sql.DataSource介面
- 建立批量的Connection用LinkedList儲存【既然是個池,當然用集合儲存、、LinkedList底層是連結串列,對增刪效能較好】
- 實現getConnetion(),讓getConnection()每次呼叫,都是在LinkedList中取一個Connection返回給使用者
- 呼叫Connection.close()方法,Connction返回給LinkedList
private static LinkedList<Connection> list = new LinkedList<>();
//獲取連線只需要一次就夠了,所以用static程式碼塊
static {
//讀取檔案配置
InputStream inputStream = Demo1.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
try {
properties.load(inputStream);
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String driver = properties.getProperty("driver");
String password = properties.getProperty("password");
//載入驅動
Class.forName(driver);
//獲取多個連線,儲存在LinkedList集合中
for (int i = 0; i < 10; i++) {
Connection connection = DriverManager.getConnection(url, username, password);
list.add(connection);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
//重寫Connection方法,使用者獲取連線應該從LinkedList中給他
@Override
public Connection getConnection() throws SQLException {
System.out.println(list.size());
System.out.println(list);
//先判斷LinkedList是否存在連線
return list.size() > 0 ? list.removeFirst() : null;
}
複製程式碼
我們已經完成前三步了,現在問題來了**。我們呼叫Conncetion.close()方法,是把資料庫的物理連線關掉,而不是返回給LinkedList的**
解決思路:
- 寫一個Connection子類,覆蓋close()方法
- 寫一個Connection包裝類,增強close()方法
- 用動態代理,返回一個代理物件出去,攔截close()方法的呼叫,對close()增強
分析第一個思路:
- Connection是通過資料庫驅動載入的,儲存了資料的資訊。寫一個子類Connection,new出物件,子類的Connction無法直接繼承父類的資料資訊,也就是說子類的Connection是無法連線資料庫的,更別談覆蓋close()方法了。
分析第二個思路:
- 寫一個Connection包裝類。
- 寫一個類,實現與被增強物件的相同介面【Connection介面】
- 定義一個變數,指向被增強的物件
- 定義構造方法,接收被增強物件
- 覆蓋想增強的方法
- 對於不想增強的方法,直接呼叫被增強物件的方法
- 這個思路本身是沒什麼毛病的,就是實現介面時,方法太多了!,所以我們也不使用此方法
分析第三個思路程式碼實現:
@Override
public Connection getConnection() throws SQLException {
if (list.size() > 0) {
final Connection connection = list.removeFirst();
//看看池的大小
System.out.println(list.size());
//返回一個動態代理物件
return (Connection) Proxy.newProxyInstance(Demo1.class.getClassLoader(), connection.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果不是呼叫close方法,就按照正常的來呼叫
if (!method.getName().equals("close")) {
method.invoke(connection, args);
} else {
//進到這裡來,說明呼叫的是close方法
list.add(connection);
//再看看池的大小
System.out.println(list.size());
}
return null;
}
});
}
return null;
}
複製程式碼
我們上面已經能夠簡單編寫一個執行緒池了。下面我們來使用一下開源資料庫連線池
DBCP
使用DBCP資料來源的步驟:
- 匯入兩個jar包【Commons-dbcp.jar和Commons-pool.jar】
- 讀取配置檔案
- 獲取BasicDataSourceFactory物件
- 建立DataSource物件
private static DataSource dataSource = null;
static {
try {
//讀取配置檔案
InputStream inputStream = Demo3.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties properties = new Properties();
properties.load(inputStream);
//獲取工廠物件
BasicDataSourceFactory basicDataSourceFactory = new BasicDataSourceFactory();
dataSource = basicDataSourceFactory.createDataSource(properties);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//這裡釋放資源不是把資料庫的物理連線釋放了,是把連線歸還給連線池【連線池的Connection內部自己做好了】
public static void release(Connection conn, Statement st, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if (st != null) {
try {
st.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製程式碼
C3P0
C3P0資料來源的效能更勝一籌,並且它可以使用XML配置檔案配置資訊!
步驟:
- 匯入開發包【c3p0-0.9.2-pre1.jar】和【mchange-commons-0.2.jar】
- 匯入XML配置檔案【可以在程式中自己一個一個配,C3P0的doc中的Configuration有XML檔案的事例】
- new出ComboPooledDataSource物件
private static ComboPooledDataSource comboPooledDataSource = null;
static {
//如果我什麼都不指定,就是使用XML預設的配置,這裡我指定的是oracle的
comboPooledDataSource = new ComboPooledDataSource("oracle");
}
public static Connection getConnection() throws SQLException {
return comboPooledDataSource.getConnection();
}
複製程式碼
Tomcat資料來源
Tomcat伺服器也給我們提供了連線池,內部其實就是DBCP
步驟:
- 在META-INF目錄下配置context.xml檔案【檔案內容可以在tomcat預設頁面的 JNDI Resources下Configure Tomcat's Resource Factory找到】
- 匯入Mysql或oracle開發包到tomcat的lib目錄下
- 初始化JNDI->獲取JNDI容器->檢索以XXX為名字在JNDI容器存放的連線池
context.xml檔案的配置:
<Context>
<Resource name="jdbc/EmployeeDB"
auth="Container"
type="javax.sql.DataSource"
username="root"
password="root"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/zhongfucheng"
maxActive="8"
maxIdle="4"/>
</Context>
複製程式碼
try {
//初始化JNDI容器
Context initCtx = new InitialContext();
//獲取到JNDI容器
Context envCtx = (Context) initCtx.lookup("java:comp/env");
//掃描以jdbc/EmployeeDB名字繫結在JNDI容器下的連線池
DataSource ds = (DataSource)
envCtx.lookup("jdbc/EmployeeDB");
Connection conn = ds.getConnection();
System.out.println(conn);
}
複製程式碼
使用dbutils框架
dbutils它是對JDBC的簡單封裝,極大簡化jdbc編碼的工作量
DbUtils類
提供了關閉連線,裝載JDBC驅動,回滾提交事務等方法的工具類【比較少使用,因為我們學了連線池,就應該使用連線池連線資料庫】
QueryRunner類
該類簡化了SQL查詢,配合ResultSetHandler使用,可以完成大部分的資料庫操作,過載了許多的查詢,更新,批處理方法。大大減少了程式碼量
ResultSetHandler介面
該介面規範了對ResultSet的操作,要對結果集進行什麼操作,傳入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 將ResultSet的一個列到一個物件中。
使用DbUtils框架對資料庫的CRUD
/*
* 使用DbUtils框架對資料庫的CRUD
* 批處理
*
* */
public class Test {
@org.junit.Test
public void add() throws SQLException {
//建立出QueryRunner物件
QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
String sql = "INSERT INTO student (id,name) VALUES(?,?)";
//我們發現query()方法有的需要傳入Connection物件,有的不需要傳入
//區別:你傳入Connection物件是需要你來銷燬該Connection,你不傳入,由程式幫你把Connection放回到連線池中
queryRunner.update(sql, new Object[]{"100", "zhongfucheng"});
}
@org.junit.Test
public void query()throws SQLException {
//建立出QueryRunner物件
QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
String sql = "SELECT * FROM student";
List list = (List) queryRunner.query(sql, new BeanListHandler(Student.class));
System.out.println(list.size());
}
@org.junit.Test
public void delete() throws SQLException {
//建立出QueryRunner物件
QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
String sql = "DELETE FROM student WHERE id='100'";
queryRunner.update(sql);
}
@org.junit.Test
public void update() throws SQLException {
//建立出QueryRunner物件
QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
String sql = "UPDATE student SET name=? WHERE id=?";
queryRunner.update(sql, new Object[]{"zhongfuchengaaa", 1});
}
@org.junit.Test
public void batch() throws SQLException {
//建立出QueryRunner物件
QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
String sql = "INSERT INTO student (name,id) VALUES(?,?)";
Object[][] objects = new Object[10][];
for (int i = 0; i < 10; i++) {
objects[i] = new Object[]{"aaa", i + 300};
}
queryRunner.batch(sql, objects);
}
}
複製程式碼
分頁
分頁技術是非常常見的,在搜尋引擎下搜尋頁面,不可能把全部資料都顯示在一個頁面裡邊。所以我們用到了分頁技術。
Oracle實現分頁
/*
Oracle分頁語法:
@lineSize---每頁顯示資料行數
@currentPage----當前所在頁
*/
SELECT *FROM (
SELECT 列名,列名,ROWNUM rn
FROM 表名
WHERE ROWNUM<=(currentPage*lineSize)) temp
WHERE temp.rn>(currentPage-1)*lineSize;
複製程式碼
Oracle分頁原理簡單解釋:
/*
Oracle分頁:
Oracle的分頁依賴於ROWNUM這個偽列,ROWNUM主要作用就是產生行號。
分頁原理:
1:子查詢查出前n行資料,ROWNUM產生前N行的行號
2:使用子查詢產生ROWNUM的行號,通過外部的篩選出想要的資料
例子:
我現在規定每頁顯示5行資料【lineSize=5】,我要查詢第2頁的資料【currentPage=2】
注:【對照著語法來看】
實現:
1:子查詢查出前10條資料【ROWNUM<=10】
2:外部篩選出後面5條資料【ROWNUM>5】
3:這樣我們就取到了後面5條的資料
*/
複製程式碼
Mysql實現分頁
/*
Mysql分頁語法:
@start---偏移量,不設定就是從0開始【也就是(currentPage-1)*lineSize】
@length---長度,取多少行資料
*/
SELECT *
FROM 表名
LIMIT [START], length;
/*
例子:
我現在規定每頁顯示5行資料,我要查詢第2頁的資料
分析:
1:第2頁的資料其實就是從第6條資料開始,取5條
實現:
1:start為5【偏移量從0開始】
2:length為5
*/
複製程式碼
總結:
- Mysql從(currentPage-1)*lineSize開始取資料,取lineSize條資料
- Oracle先獲取currentPage*lineSize條資料,從(currentPage-1)*lineSize開始取資料
使用JDBC連線資料庫實現分頁
下面是常見的分頁圖片
配合圖片,看下我們的需求是什麼:
- 算出有多少頁的資料,顯示在頁面上
- 根據頁碼,從資料庫顯示相對應的資料。
分析:
- 算出有多少頁資料這是非常簡單的【在資料庫中查詢有多少條記錄,你每頁顯示多少條記錄,就可以算出有多少頁資料了】
- 使用Mysql或Oracle的分頁語法即可
通過上面分析,我們會發現需要用到4個變數
- currentPage--當前頁【由使用者決定的】
- totalRecord--總資料數【查詢表可知】
- lineSize--每頁顯示資料的數量【由我們開發人員決定】
- pageCount--頁數【totalRecord和lineSize決定】
//每頁顯示3條資料
int lineSize = 3;
//總記錄數
int totalRecord = getTotalRecord();
//假設使用者指定的是第2頁
int currentPage = 2;
//一共有多少頁
int pageCount = getPageCount(totalRecord, lineSize);
//使用什麼資料庫進行分頁,記得要在JdbcUtils中改配置
List<Person> list = getPageData2(currentPage, lineSize);
for (Person person : list) {
System.out.println(person);
}
}
//使用JDBC連線Mysql資料庫實現分頁
public static List<Person> getPageData(int currentPage, int lineSize) throws SQLException {
//從哪個位置開始取資料
int start = (currentPage - 1) * lineSize;
QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
String sql = "SELECT name,address FROM person LIMIT ?,?";
List<Person> persons = (List<Person>) queryRunner.query(sql, new BeanListHandler(Person.class), new Object[]{start, lineSize});
return persons;
}
//使用JDBC連線Oracle資料庫實現分頁
public static List<Person> getPageData2(int currentPage, int lineSize) throws SQLException {
//從哪個位置開始取資料
int start = (currentPage - 1) * lineSize;
//讀取前N條資料
int end = currentPage * lineSize;
QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
String sql = "SELECT " +
" name, " +
" address " +
"FROM ( " +
" SELECT " +
" name, " +
" address , " +
" ROWNUM rn " +
" FROM person " +
" WHERE ROWNUM <= ? " +
")temp WHERE temp.rn>?";
List<Person> persons = (List<Person>) queryRunner.query(sql, new BeanListHandler(Person.class), new Object[]{end, start});
return persons;
}
public static int getPageCount(int totalRecord, int lineSize) {
//簡單演算法
//return (totalRecord - 1) / lineSize + 1;
//此演算法比較好理解,把資料代代進去就知道了。
return totalRecord % lineSize == 0 ? (totalRecord / lineSize) : (totalRecord / lineSize) + 1;
}
public static int getTotalRecord() throws SQLException {
//使用DbUtils框架查詢資料庫表中有多少條資料
QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
String sql = "SELECT COUNT(*) FROM person";
Object o = queryRunner.query(sql, new ScalarHandler());
String ss = o.toString();
int s = Integer.parseInt(ss);
return s;
}
複製程式碼
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章的同學,可以關注微信公眾號:Java3y。