1.數(shù)據(jù)庫(kù)連接池
什么是數(shù)據(jù)庫(kù)連接池
簡(jiǎn)單來(lái)說(shuō):數(shù)據(jù)庫(kù)連接池就是提供連接的。硬纤。解滓。
為什么我們要使用數(shù)據(jù)庫(kù)連接池
- 數(shù)據(jù)庫(kù)的連接的建立和關(guān)閉是非常消耗資源的
- 頻繁地打開(kāi)、關(guān)閉連接造成系統(tǒng)性能低下
編寫(xiě)連接池
- 編寫(xiě)連接池需實(shí)現(xiàn)java.sql.DataSource接口
- 創(chuàng)建批量的Connection用LinkedList保存【既然是個(gè)池筝家,當(dāng)然用集合保存洼裤、、LinkedList底層是鏈表溪王,對(duì)增刪性能較好】
- 實(shí)現(xiàn)getConnetion()腮鞍,讓getConnection()每次調(diào)用值骇,都是在LinkedList中取一個(gè)Connection返回給用戶
- 調(diào)用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");
//加載驅(qū)動(dòng)
Class.forName(driver);
//獲取多個(gè)連接雷客,保存在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();
}
}
//重寫(xiě)Connection方法,用戶獲取連接應(yīng)該從LinkedList中給他
@Override
public Connection getConnection() throws SQLException {
System.out.println(list.size());
System.out.println(list);
//先判斷LinkedList是否存在連接
return list.size() > 0 ? list.removeFirst() : null;
}
我們已經(jīng)完成前三步了桥狡,現(xiàn)在問(wèn)題來(lái)了搅裙。我們調(diào)用Conncetion.close()方法,是把數(shù)據(jù)庫(kù)的物理連接關(guān)掉裹芝,而不是返回給LinkedList的
解決思路:
- 寫(xiě)一個(gè)Connection子類(lèi)部逮,覆蓋close()方法
- 寫(xiě)一個(gè)Connection包裝類(lèi),增強(qiáng)close()方法
- 用動(dòng)態(tài)代理嫂易,返回一個(gè)代理對(duì)象出去兄朋,攔截close()方法的調(diào)用,對(duì)close()增強(qiáng)
分析第一個(gè)思路:
- Connection是通過(guò)數(shù)據(jù)庫(kù)驅(qū)動(dòng)加載的怜械,保存了數(shù)據(jù)的信息颅和。寫(xiě)一個(gè)子類(lèi)Connection,new出對(duì)象缕允,子類(lèi)的Connction無(wú)法直接繼承父類(lèi)的數(shù)據(jù)信息峡扩,也就是說(shuō)子類(lèi)的Connection是無(wú)法連接數(shù)據(jù)庫(kù)的,更別談覆蓋close()方法了障本。
分析第二個(gè)思路:
- 寫(xiě)一個(gè)Connection包裝類(lèi)教届。
- 寫(xiě)一個(gè)類(lèi),實(shí)現(xiàn)與被增強(qiáng)對(duì)象的相同接口【Connection接口】
- 定義一個(gè)變量驾霜,指向被增強(qiáng)的對(duì)象
- 定義構(gòu)造方法案训,接收被增強(qiáng)對(duì)象
- 覆蓋想增強(qiáng)的方法
- 對(duì)于不想增強(qiáng)的方法,直接調(diào)用被增強(qiáng)對(duì)象的方法
- 這個(gè)思路本身是沒(méi)什么毛病的粪糙,就是實(shí)現(xiàn)接口時(shí)强霎,方法太多了!蓉冈,所以我們也不使用此方法
分析第三個(gè)思路代碼實(shí)現(xiàn):
@Override
public Connection getConnection() throws SQLException {
if (list.size() > 0) {
final Connection connection = list.removeFirst();
//看看池的大小
System.out.println(list.size());
//返回一個(gè)動(dòng)態(tài)代理對(duì)象
return (Connection) Proxy.newProxyInstance(Demo1.class.getClassLoader(), connection.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果不是調(diào)用close方法城舞,就按照正常的來(lái)調(diào)用
if (!method.getName().equals("close")) {
method.invoke(connection, args);
} else {
//進(jìn)到這里來(lái),說(shuō)明調(diào)用的是close方法
list.add(connection);
//再看看池的大小
System.out.println(list.size());
}
return null;
}
});
}
return null;
}
我們上面已經(jīng)能夠簡(jiǎn)單編寫(xiě)一個(gè)線程池了洒擦。下面我們來(lái)使用一下開(kāi)源數(shù)據(jù)庫(kù)連接池
DBCP
使用DBCP數(shù)據(jù)源的步驟:
- 導(dǎo)入兩個(gè)jar包【Commons-dbcp.jar和Commons-pool.jar】
- 讀取配置文件
- 獲取BasicDataSourceFactory對(duì)象
- 創(chuàng)建DataSource對(duì)象
private static DataSource dataSource = null;
static {
try {
//讀取配置文件
InputStream inputStream = Demo3.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties properties = new Properties();
properties.load(inputStream);
//獲取工廠對(duì)象
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();
}
//這里釋放資源不是把數(shù)據(jù)庫(kù)的物理連接釋放了椿争,是把連接歸還給連接池【連接池的Connection內(nèi)部自己做好了】
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數(shù)據(jù)源的性能更勝一籌,并且它可以使用XML配置文件配置信息熟嫩!
步驟:
- 導(dǎo)入開(kāi)發(fā)包【c3p0-0.9.2-pre1.jar】和【mchange-commons-0.2.jar】
- 導(dǎo)入XML配置文件【可以在程序中自己一個(gè)一個(gè)配秦踪,C3P0的doc中的Configuration有XML文件的事例】
- new出ComboPooledDataSource對(duì)象
private static ComboPooledDataSource comboPooledDataSource = null;
static {
//如果我什么都不指定,就是使用XML默認(rèn)的配置,這里我指定的是oracle的
comboPooledDataSource = new ComboPooledDataSource("oracle");
}
public static Connection getConnection() throws SQLException {
return comboPooledDataSource.getConnection();
}
Tomcat數(shù)據(jù)源
Tomcat服務(wù)器也給我們提供了連接池椅邓,內(nèi)部其實(shí)就是DBCP
步驟:
- 在META-INF目錄下配置context.xml文件【文件內(nèi)容可以在tomcat默認(rèn)頁(yè)面的 JNDI Resources下Configure Tomcat's Resource Factory找到】
- 導(dǎo)入Mysql或oracle開(kāi)發(fā)包到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它是對(duì)JDBC的簡(jiǎn)單封裝柠逞,極大簡(jiǎn)化jdbc編碼的工作量
DbUtils類(lèi)
提供了關(guān)閉連接,裝載JDBC驅(qū)動(dòng)景馁,回滾提交事務(wù)等方法的工具類(lèi)【比較少使用板壮,因?yàn)槲覀儗W(xué)了連接池,就應(yīng)該使用連接池連接數(shù)據(jù)庫(kù)】
QueryRunner類(lèi)
該類(lèi)簡(jiǎn)化了SQL查詢合住,配合ResultSetHandler使用绰精,可以完成大部分的數(shù)據(jù)庫(kù)操作,重載了許多的查詢透葛,更新笨使,批處理方法。大大減少了代碼量
ResultSetHandler接口
該接口規(guī)范了對(duì)ResultSet的操作僚害,要對(duì)結(jié)果集進(jìn)行什么操作硫椰,傳入ResultSetHandler接口的實(shí)現(xiàn)類(lèi)即可。
- ArrayHandler:把結(jié)果集中的第一行數(shù)據(jù)轉(zhuǎn)成對(duì)象數(shù)組萨蚕。
- ArrayListHandler:把結(jié)果集中的每一行數(shù)據(jù)都轉(zhuǎn)成一個(gè)數(shù)組靶草,再存放到List中。
- BeanHandler:將結(jié)果集中的第一行數(shù)據(jù)封裝到一個(gè)對(duì)應(yīng)的JavaBean實(shí)例中岳遥。
- BeanListHandler:將結(jié)果集中的每一行數(shù)據(jù)都封裝到一個(gè)對(duì)應(yīng)的JavaBean實(shí)例中奕翔,存放到List里。
- ColumnListHandler:將結(jié)果集中某一列的數(shù)據(jù)存放到List中寒随。
- KeyedHandler(name):將結(jié)果集中的每一行數(shù)據(jù)都封裝到一個(gè)Map里糠悯,再把這些map再存到一個(gè)map里帮坚,其key為指定的key妻往。
- MapHandler:將結(jié)果集中的第一行數(shù)據(jù)封裝到一個(gè)Map里,key是列名试和,value就是對(duì)應(yīng)的值讯泣。
- MapListHandler:將結(jié)果集中的每一行數(shù)據(jù)都封裝到一個(gè)Map里,然后再存放到List
- ScalarHandler 將ResultSet的一個(gè)列到一個(gè)對(duì)象中阅悍。
使用DbUtils框架對(duì)數(shù)據(jù)庫(kù)的CRUD
/*
* 使用DbUtils框架對(duì)數(shù)據(jù)庫(kù)的CRUD
* 批處理
*
* */
public class Test {
@org.junit.Test
public void add() throws SQLException {
//創(chuàng)建出QueryRunner對(duì)象
QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
String sql = "INSERT INTO student (id,name) VALUES(?,?)";
//我們發(fā)現(xiàn)query()方法有的需要傳入Connection對(duì)象好渠,有的不需要傳入
//區(qū)別:你傳入Connection對(duì)象是需要你來(lái)銷(xiāo)毀該Connection,你不傳入节视,由程序幫你把Connection放回到連接池中
queryRunner.update(sql, new Object[]{"100", "zhongfucheng"});
}
@org.junit.Test
public void query()throws SQLException {
//創(chuàng)建出QueryRunner對(duì)象
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 {
//創(chuàng)建出QueryRunner對(duì)象
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 {
//創(chuàng)建出QueryRunner對(duì)象
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 {
//創(chuàng)建出QueryRunner對(duì)象
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);
}
}
分頁(yè)
分頁(yè)技術(shù)是非常常見(jiàn)的拳锚,在搜索引擎下搜索頁(yè)面,不可能把全部數(shù)據(jù)都顯示在一個(gè)頁(yè)面里邊寻行。所以我們用到了分頁(yè)技術(shù)霍掺。
Oracle實(shí)現(xiàn)分頁(yè)
/*
Oracle分頁(yè)語(yǔ)法:
@lineSize---每頁(yè)顯示數(shù)據(jù)行數(shù)
@currentPage----當(dāng)前所在頁(yè)
*/
SELECT *FROM (
SELECT 列名,列名,ROWNUM rn
FROM 表名
WHERE ROWNUM<=(currentPage*lineSize)) temp
WHERE temp.rn>(currentPage-1)*lineSize;
Oracle分頁(yè)原理簡(jiǎn)單解釋:
/*
Oracle分頁(yè):
Oracle的分頁(yè)依賴于ROWNUM這個(gè)偽列,ROWNUM主要作用就是產(chǎn)生行號(hào)。
分頁(yè)原理:
1:子查詢查出前n行數(shù)據(jù)杆烁,ROWNUM產(chǎn)生前N行的行號(hào)
2:使用子查詢產(chǎn)生ROWNUM的行號(hào)牙丽,通過(guò)外部的篩選出想要的數(shù)據(jù)
例子:
我現(xiàn)在規(guī)定每頁(yè)顯示5行數(shù)據(jù)【lineSize=5】,我要查詢第2頁(yè)的數(shù)據(jù)【currentPage=2】
注:【對(duì)照著語(yǔ)法來(lái)看】
實(shí)現(xiàn):
1:子查詢查出前10條數(shù)據(jù)【ROWNUM<=10】
2:外部篩選出后面5條數(shù)據(jù)【ROWNUM>5】
3:這樣我們就取到了后面5條的數(shù)據(jù)
*/
Mysql實(shí)現(xiàn)分頁(yè)
/*
Mysql分頁(yè)語(yǔ)法:
@start---偏移量兔魂,不設(shè)置就是從0開(kāi)始【也就是(currentPage-1)*lineSize】
@length---長(zhǎng)度烤芦,取多少行數(shù)據(jù)
*/
SELECT *
FROM 表名
LIMIT [START], length;
/*
例子:
我現(xiàn)在規(guī)定每頁(yè)顯示5行數(shù)據(jù),我要查詢第2頁(yè)的數(shù)據(jù)
分析:
1:第2頁(yè)的數(shù)據(jù)其實(shí)就是從第6條數(shù)據(jù)開(kāi)始析校,取5條
實(shí)現(xiàn):
1:start為5【偏移量從0開(kāi)始】
2:length為5
*/
總結(jié):
- Mysql從(currentPage-1)*lineSize開(kāi)始取數(shù)據(jù)构罗,取lineSize條數(shù)據(jù)
- Oracle先獲取currentPagelineSize條數(shù)據(jù),從(currentPage-1)lineSize開(kāi)始取數(shù)據(jù)
使用JDBC連接數(shù)據(jù)庫(kù)實(shí)現(xiàn)分頁(yè)
下面是常見(jiàn)的分頁(yè)圖片
配合圖片智玻,看下我們的需求是什么:
- 算出有多少頁(yè)的數(shù)據(jù)绰播,顯示在頁(yè)面上
- 根據(jù)頁(yè)碼,從數(shù)據(jù)庫(kù)顯示相對(duì)應(yīng)的數(shù)據(jù)尚困。
分析:
- 算出有多少頁(yè)數(shù)據(jù)這是非常簡(jiǎn)單的【在數(shù)據(jù)庫(kù)中查詢有多少條記錄蠢箩,你每頁(yè)顯示多少條記錄,就可以算出有多少頁(yè)數(shù)據(jù)了】
- 使用Mysql或Oracle的分頁(yè)語(yǔ)法即可
通過(guò)上面分析事甜,我們會(huì)發(fā)現(xiàn)需要用到4個(gè)變量
- currentPage--當(dāng)前頁(yè)【由用戶決定的】
- totalRecord--總數(shù)據(jù)數(shù)【查詢表可知】
- lineSize--每頁(yè)顯示數(shù)據(jù)的數(shù)量【由我們開(kāi)發(fā)人員決定】
- pageCount--頁(yè)數(shù)【totalRecord和lineSize決定】
//每頁(yè)顯示3條數(shù)據(jù)
int lineSize = 3;
//總記錄數(shù)
int totalRecord = getTotalRecord();
//假設(shè)用戶指定的是第2頁(yè)
int currentPage = 2;
//一共有多少頁(yè)
int pageCount = getPageCount(totalRecord, lineSize);
//使用什么數(shù)據(jù)庫(kù)進(jìn)行分頁(yè)谬泌,記得要在JdbcUtils中改配置
List<Person> list = getPageData2(currentPage, lineSize);
for (Person person : list) {
System.out.println(person);
}
}
//使用JDBC連接Mysql數(shù)據(jù)庫(kù)實(shí)現(xiàn)分頁(yè)
public static List<Person> getPageData(int currentPage, int lineSize) throws SQLException {
//從哪個(gè)位置開(kāi)始取數(shù)據(jù)
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數(shù)據(jù)庫(kù)實(shí)現(xiàn)分頁(yè)
public static List<Person> getPageData2(int currentPage, int lineSize) throws SQLException {
//從哪個(gè)位置開(kāi)始取數(shù)據(jù)
int start = (currentPage - 1) * lineSize;
//讀取前N條數(shù)據(jù)
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) {
//簡(jiǎn)單算法
//return (totalRecord - 1) / lineSize + 1;
//此算法比較好理解,把數(shù)據(jù)代代進(jìn)去就知道了逻谦。
return totalRecord % lineSize == 0 ? (totalRecord / lineSize) : (totalRecord / lineSize) + 1;
}
public static int getTotalRecord() throws SQLException {
//使用DbUtils框架查詢數(shù)據(jù)庫(kù)表中有多少條數(shù)據(jù)
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;
}
如果文章有錯(cuò)的地方歡迎指正掌实,大家互相交流。習(xí)慣在微信看技術(shù)文章的同學(xué)邦马,可以關(guān)注微信公眾號(hào):Java3y贱鼻。