一、元數(shù)據(jù)(DataBaseMetaData)
元數(shù)據(jù):數(shù)據(jù)庫、表、列的定義信息
Connection.getDatabaseMetaData()獲得元數(shù)據(jù)DataBaseMetaData對(duì)象方法
getURL():返回一個(gè)String類對(duì)象,代表數(shù)據(jù)庫的URL。
getUserName():返回連接當(dāng)前數(shù)據(jù)庫管理系統(tǒng)的用戶名。
getDatabaseProductName():返回?cái)?shù)據(jù)庫的產(chǎn)品名稱。
getDatabaseProductVersion():返回?cái)?shù)據(jù)庫的版本號(hào)。
getDriverName():返回驅(qū)動(dòng)驅(qū)動(dòng)程序的名稱。
getDriverVersion():返回驅(qū)動(dòng)程序的版本號(hào)。
isReadOnly():返回一個(gè)boolean值,指示數(shù)據(jù)庫是否只允許讀操作。
工程(day16)
例1.獲取數(shù)據(jù)庫的元信息(Demo4.java)
package cn.itcast.demo;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import cn.itcast.utils.JdbcUtils_C3P0;
public class Demo4 {
public static void main(String[] args) throws SQLException {
Connection connection = JdbcUtils_C3P0.getConnection();
DatabaseMetaData metaData = connection.getMetaData();
System.out.println(metaData.getDatabaseProductName());
System.out.println(metaData.getDriverName());
}
}
二、元數(shù)據(jù)(ParameterMetaData)
PreparedStatement.getParameterMetaData()獲得代表PreparedStatement元數(shù)據(jù)的ParameterMetaData對(duì)象ParameterMetaData對(duì)象方法
getParameterCount()獲得指定參數(shù)的個(gè)數(shù)
getParameterType(int param)獲得指定參數(shù)的sql類型
例2.獲取數(shù)據(jù)庫的元信息(Demo5.java)
package cn.itcast.demo;
import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import cn.itcast.utils.JdbcUtils_C3P0;
public class Demo5 {
public static void main(String[] args) throws SQLException {
Connection connection = JdbcUtils_C3P0.getConnection();
String sql = "select * from user where name = ? and password = ?";
PreparedStatement ps = connection.prepareStatement(sql);
ParameterMetaData pm = ps.getParameterMetaData();
//得到參數(shù)的個(gè)數(shù)
System.out.println(pm.getParameterCount());
System.out.println(pm.getParameterType(1));
}
}
三、元數(shù)據(jù)(ResultSetMetaData)
ResultSet.getMetaData()獲得代表ResultSet對(duì)象元數(shù)據(jù)的ResultSetMetaData對(duì)象。ResultSetMetaData對(duì)象
getColumnCount()返回resultset對(duì)象的列數(shù)
getColumnName(int column)獲得指定列的名稱
getColumnTypeName(int column)獲得指定列的類型
例3.獲取數(shù)據(jù)庫元信息(Demo6.java)
package cn.itcast.demo;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import cn.itcast.utils.JdbcUtils_C3P0;
public class Demo6 {
public static void main(String[] args) throws SQLException {
Connection connection = JdbcUtils_C3P0.getConnection();
String sql = "select * from account";
PreparedStatement pst = connection.prepareStatement(sql);
ResultSet result = pst.executeQuery();
ResultSetMetaData metadata = result.getMetaData();
System.out.println(metadata.getColumnCount());//有3列數(shù)據(jù)
System.out.println(metadata.getColumnName(1));
}
}
四、使用元數(shù)據(jù)簡化JDBC代碼
業(yè)務(wù)背景:系統(tǒng)中所有實(shí)體對(duì)象都涉及到基本的CRUD操作。所有實(shí)體的CUD操作代碼基本相同,僅僅是發(fā)送給數(shù)據(jù)庫的sql語句不同而已,因此可以把CUD操作的所有相同代碼抽取到工具類的一個(gè)update方法中,并定義參數(shù)接收變化的sql語句。
實(shí)體的R操作,除sql語句不同之外,根據(jù)操作的實(shí)體不同,對(duì)ResultSet的映射也各不相同,因此可定義一個(gè)query方法,除以參數(shù)形式接收變化的sql語句外,可以使用策略模式由query方法的調(diào)用者決定如何把ResultSet中的數(shù)據(jù)映射到實(shí)體對(duì)象中。
這里我們通過編寫一個(gè)自己的框架來進(jìn)行說明(工程day16):
我們先定義一個(gè)bean(使用的是day16這個(gè)數(shù)據(jù)庫):
Account.java
package cn.itcast.domain;
public class Account {
private int id ;
private String name ;
private float money ;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getMoney() {
return money;
}
public void setMoney(float money) {
this.money = money;
}
}
對(duì)數(shù)據(jù)庫工具類進(jìn)行改進(jìn):
JdbcUtils.java
package cn.itcast.utils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class JdbcUtils {
private static ComboPooledDataSource ds = null;
static {
try {
ds = new ComboPooledDataSource();
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public static Connection getConnection() throws SQLException{
return ds.getConnection();
}
public static void release(Connection conn, Statement ps , ResultSet result){
if(result != null){
try {
result.close();
} catch (Exception e) {
e.printStackTrace();
}
result = null;
}
if(ps != null){
try {
ps.close();
} catch (Exception e) {
e.printStackTrace();
}
ps = null;
}
if(conn != null){
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
conn = null;
}
}
//除了sql語句和相關(guān)的參數(shù)不同外其他的代碼都一樣,這里可以替換我們之前在dao層中寫的增刪改方法
public static void update(String sql, Object[] params) throws SQLException{
Connection connection = null;
PreparedStatement ps = null;
ResultSet result = null ;
try{
connection = getConnection();
ps = connection.prepareStatement(sql);
for(int i = 0; i < params.length; i++){
ps.setObject(i + 1, params[i]);
}
ps.executeUpdate();
}finally{
release(connection, ps, result);
}
}
public static Object query(String sql, Object params[], ResultSetHandler rh) throws SQLException{
Connection conn = null ;
PreparedStatement ps = null ;
ResultSet result = null;
try{
conn = getConnection();
ps = conn.prepareStatement(sql);
for(int i = 0; i < params.length; i++){
ps.setObject(i + 1, params[i]);
}
result = ps.executeQuery();
return rh.handler(result);
}finally{
release(conn, ps, result);
}
}
}
說明:
- 1.對(duì)于增刪改方法來說,可以將其相同部分抽取出來,其不同的地方只有sql語句和相關(guān)的參數(shù)值,這里我們讓用戶將這兩個(gè)參數(shù)值傳遞進(jìn)來即可。
- 2.對(duì)于查方法來說就有點(diǎn)不一樣了,首先是最后執(zhí)行的方法是executeQuery方法,其次是有返回值,當(dāng)然對(duì)于前一個(gè)不同來說,我們只需要將executeUpdate換成executeQuery方法即可,但是對(duì)于結(jié)果集的處理來說就有點(diǎn)麻煩了。首先我們不知道返回的結(jié)果集是什么,當(dāng)然這可以使用Object代替,但是我們不知道針對(duì)結(jié)果集如何處理,于是這里我們將結(jié)果集的處理方式交給用戶。我們定義了一個(gè)接口讓用戶來實(shí)現(xiàn):
ResultSetHandler.java
package cn.itcast.utils;
import java.sql.ResultSet;
public interface ResultSetHandler {
public Object handler(ResultSet result);
}
- 3.可以發(fā)現(xiàn),讓用戶去寫處理代碼同樣不太好,這樣我們的框架就沒有達(dá)到簡化的效果,同時(shí)我們也看到一般用戶查到相關(guān)的數(shù)據(jù)之后是會(huì)封裝到一個(gè)javabean中去的,于是這里我們幫用戶實(shí)現(xiàn)一個(gè)基本的處理方法:
BeanHandler.java
package cn.itcast.dao;
import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import cn.itcast.utils.ResultSetHandler;
public class BeanHandler implements ResultSetHandler {
private Class clazz;
public BeanHandler() {
}
public BeanHandler(Class clazz) {
this.clazz = clazz;
}
@Override
public Object handler(ResultSet result) {
//這里有個(gè)條件是表中字段名和bean中的屬性名必須一致
try{
if(!result.next()){
return null;
}
//實(shí)例化對(duì)象
Object bean = clazz.newInstance();
ResultSetMetaData metaData = result.getMetaData();
int columnCount = metaData.getColumnCount();//得到結(jié)果集中數(shù)據(jù)列數(shù)
for(int i = 0; i < columnCount; i++){
//獲得每一列的列名
String columnName = metaData.getColumnName(i + 1);
Object columnData = result.getObject(i + 1);
Field f = clazz.getDeclaredField(columnName);//反射出類上列名對(duì)應(yīng)的屬性
f.setAccessible(true);//暴力反射
f.set(bean, columnData);//在賦值的時(shí)候可以使用beanUtils框架工具
}
return bean;
}catch(Exception e){
throw new RuntimeException(e);
}
}
}
說明:對(duì)于類的類型我們是不知道的,這里我們讓用戶在new的時(shí)候傳遞進(jìn)來。然后我們就可以通過反射的方式將查詢到的數(shù)據(jù)封裝到用戶傳遞進(jìn)來的類的各個(gè)屬性上去,但是這里實(shí)現(xiàn)的并不是很好,必須讓表字段名和bean中的屬性名一致。
- 4.當(dāng)然很顯然,有時(shí)候查出來的數(shù)據(jù)是一個(gè)集合,而不是像上面那種一個(gè)單一對(duì)象,于是我們又實(shí)現(xiàn)了針對(duì)集合的處理類:
BeanListHandler.java
package cn.itcast.dao;
import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
import cn.itcast.utils.ResultSetHandler;
public class BeanListHandler implements ResultSetHandler {
private Class clazz;
public BeanListHandler() {
}
public BeanListHandler(Class clazz) {
this.clazz = clazz;
}
@Override
public Object handler(ResultSet result) {
try {
List list = new ArrayList();
while(result.next()){
Object bean = clazz.newInstance();
ResultSetMetaData metaData = result.getMetaData();
int count = metaData.getColumnCount();
for(int i = 0; i < count; i++){
String name = metaData.getColumnName(i + 1);
Object value = result.getObject(name);
Field f = bean.getClass().getDeclaredField(name);
f.setAccessible(true);
f.set(bean, value);
}
list.add(bean);
}
return list.size() > 0? list:null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
- 5.此時(shí)我們的增刪改查工具類就可以簡化為如下:
AccountDao.java
package cn.itcast.dao;
import java.sql.SQLException;
import java.util.List;
import cn.itcast.domain.Account;
import cn.itcast.utils.JdbcUtils;
public class AccountDao {
public void add(Account account) throws SQLException{
String sql = "insert into account(name , money) values(?, ?)";
Object[] params = {account.getName(), account.getMoney()};
JdbcUtils.update(sql, params);
}
public void delete(int id ) throws SQLException{
String sql = "delete from account where id = ?";
Object[] params = {id};
JdbcUtils.update(sql, params);
}
public void update(Account account) throws SQLException{
String sql = "update account set name = ?, money = ? where id = ?";
Object params[] = {account.getName(), account.getMoney(), account.getId()};
JdbcUtils.update(sql, params);
}
public Account find(int id ) throws SQLException{
String sql = "select * from account where id = ?";
Object params[] = {id};
return (Account) JdbcUtils.query(sql, params, new BeanHandler(Account.class));
}
public List getAll() throws SQLException{
String sql = "select * from account";
Object params[] = {};
return (List)JdbcUtils.query(sql, params, new BeanListHandler(Account.class));
}
}
- 6.測(cè)試
Demo7.java
package cn.itcast.demo;
import java.sql.SQLException;
import java.util.List;
import cn.itcast.dao.AccountDao;
import cn.itcast.domain.Account;
public class Demo7 {
public static void main(String[] args) throws SQLException {
AccountDao dao = new AccountDao();
Account a = dao.find(1);
System.out.println(a.getName());
List list = dao.getAll();
System.out.println(list.size());
}
}
最后:這里編寫一個(gè)JDBC框架的目的并不是要實(shí)現(xiàn)一個(gè)真正可用的框架,主要是加深對(duì)JDBC各類框架實(shí)現(xiàn)的原理的理解。