JDBC【數據庫連接池、DbUtils框架、分頁】
阿新 • • 發佈:2018-02-20
rom cda 連接 開源 over rownum cal 每次 常見
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先獲取currentPagelineSize條數據,從(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。
JDBC【數據庫連接池、DbUtils框架、分頁】