JDBC
一、数据库的连接
1.Driver
可以通过Driver类的connect方法连接数据库
[注:以下所有代码处理异常时都应该使用 try-catch-finally 形式,将资源的关闭代码块放在finally中 ,为了书写方便以下代码只是简单的 throws ,在实际开发中一定要使用 try-catch-finally 处理。]
示例代码:
@Test
public void test1() throws Exception{
Driver driver = new Driver();
String url = "jdbc:mysql://localhost:3306/test";//连接MySql数据库
Properties info = new Properties();
info.put("user", "root");//数据库用户名
info.put("password", "123456");//密码
Connection conn = driver.connect(url, info);
System.out.println(conn);
conn.close();
}
2.DriverManager
对 Driver 进行了封装,在JDBC操作中,DriverManager 就是一个工厂类,专门负责取得Connection 接口的实例化对象,通过Class.forName()可以加载指定子类的名称(包,类).
开发中,通常将数据库连接信息放在 Properties 文件中,通过输入流读取数据库连接信息,当需要更改数据库连接信息时只需更改 Properties 文件中的信息,从而大大提高了开发的效率。
Properties文件:
driver = com.mysql.jdbc.Driver
url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8
user = root
password = 123456
示例代码:
public Connection getConnection() throws Exception{
String driver;
String url;
String user;
String password;
//获取数据库连接文件输入流
InputStream in = getClass().getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(in);//加载输入流
driver = properties.getProperty("driver");//读取信息
url = properties.getProperty("url");
user = properties.getProperty("user");
password = properties.getProperty("password");
Class.forName(driver); //加载驱动程序
return DriverManager.getConnection(url, user, password);//返回一个Connection对象
}
二、Statement接口操作数据库
此接口可以通过 Connection 接口中的 createStatement() 方法实例化
此接口中定义了如下的常用的方法:
- public int executeUpdate(String sql)z:执行数据库更新的SQL语句,例如 insert、update、delete 等语句,返回更新的记录数。
- public ResultSet exceuteQuery(String sql):执行数据库查询操作返回一个结果集对象。
- public void close():关闭操作。
示例代码:
@Test
public void testInsert() throws Exception{
Connection conn = getConnection();
Statement sta = null;
String sql = "insert into customers(name,age) values('张三','21')";
sta = conn.createStatement();
int len = sta.executeUpdate(sql);
System.out.println("更新行数:" + len);
sta.close();
conn.close();
}
注意:使用 Statement 接口不能避免注入式攻击
@Test
public void testSQLInjection(){
String name = "a' OR age = ";
String age = "OR '1' = '1";
String sql = "select * from customers where name = '"+name+"'"
+ "and age = '"+age+"'";
System.out.println(sql);
Connection conn = null;
Statement sta = null;
ResultSet rs = null;
try {
conn = getConnection();
sta = conn.createStatement();
rs = sta.executeQuery(sql);
if(rs.next()){
System.out.println("恭喜你,登陆成功!");
}else{
System.out.println("登陆失败,请检查用户名及密码!");
}
} catch (Exception e) {
e.printStackTrace();
}finally{
release(conn, sta, rs);
}
}
如上代码,当 SQL 语句拼接成 select * from customers where name = ‘a’ OR age = ‘and age = ‘OR ‘1’ = ‘1’ 可以成功访问到数据库的数据
使用 PreparedStatement 可以防止 SQL 注入式攻击
三、ResultSet 接口进行查询
当所有的记录返回到 ResultSet 时,所有的内容都是按照数据类型存放的,所以用户只需按照数据类型一行行读取数据即可
ResultSet 常用方法如下:
- public boolean next():移动指针并判断是否有数据。
- public 数据 getXxx(列的标记):获取指定类型的数据。
- public void close():关闭结果集。
示例代码:
@Test
public void testSelect() throws Exception{
Connection conn = getConnection();
Statement sta = null;
String sql = "select id, name, age from customers";
sta = conn.createStatement();
ResultSet res = sta.executeQuery(sql); //获取结果集
while(res.next()){
System.out.print(res.getInt(1) + " ");//索引和字段名都可以
System.out.print(res.getString(2) + " ");
System.out.println(res.getString("age"));
}
res.close();
sta.close();
conn.close();
}
提示:Result 中所有的数据都可以通过 getString() 方法取得
在获取相应字段的值时,通过字段名和索引号都可以。
四、PreparedStatement 接口
PreparedStatement 是 Statement 的子接口,属于预操作处理
PreparedStatement 的优点:
- 对象已经预编译过,执行速度比 Statement 快。
- 代码简洁。
- PreparedStatement中用 ‘?’ 占位参数,所以可以 有效的防止 SQL 注入
PreparedStatement 常用的方法:
- public int executeUpdate():执行设置的预处理 SQL 语句
- public ResultSet executeQuery():执行数据库查询操作,返回 ResultSet
- public void set 数据类型(int parameterIndex,数据类型 x):指定要设置的索引号,并设置数据
- public void setDate(int parameterIndex,Date x):指定要设置的索引号,并设置 java.sql.Date 类型的日期内容
@Test
public void testPreparedStatement(){
Connection conn = null;
PreparedStatement ps = null;
try{
conn = getConnection();
String sql = "insert into customers(name,age) values(?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "ZS");
ps.setInt(2, 21);
int num = ps.executeUpdate();
System.out.println("更新行数:" + num);
}catch(Exception e){
e.printStackTrace();
}finally{
try {
if(conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(ps != null)
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
五、ResultSetMetaData接口
ResultSetMetaData 接口用于获取ResultSet对象中列的类型和属性信息的对象。
常用的方法:
- public int getColumnCount():获取结果集中列的数目
- public String getColumnLabel(int index):获取指定索引的列名。
注意:索引都是从 1 开始的。
开发步骤:
获取ResultSetMetaData对象:
在结果集中将所有的结果按键值对的形式存放在 Map 中
- 获取每一列的数据
- 通过反射实例化相应的对象
- 通过反射为相应对象赋值,属性即为 Map 的键,属性值为 Map 的值
示例代码:
@Test
public void testResultSetMetaData(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
String sql = "select name name,age age from customers where name = ?";
conn = JDBCTools.getConnection();
ps = conn.prepareStatement(sql);
ps.setString(1, "ZS");
rs = ps.executeQuery();
//1.获取ResultSetMetaData对象
ResultSetMetaData rsmd = rs.getMetaData();
//2.在结果集中将所有的结果按键值对的形式存放在 Map 中
Map<String, Object> values = new HashMap<>();
while(rs.next()){
//3.获取每一列的数据
for(int i = 0;i < rsmd.getColumnCount();i++){
String columnLabel = rsmd.getColumnLabel(i+1);//获取列名
Object columnObject = rs.getObject(columnLabel);//获取该列的数据
values.put(columnLabel, columnObject);
}
}
//4.通过反射实例化相应的对象
Class clazz = Customers.class;
Object object = clazz.newInstance();
//5.通过反射为相应对象赋值,属性即为 Map 的键,属性值为 Map 的值
for (Map.Entry<String, Object> entry:values.entrySet()) {
String fieldName = entry.getKey();
Object fieldValue = entry.getValue();
Field field = clazz.getDeclaredField(fieldName);
field.set(object, fieldValue);
//System.out.println(fieldName + ":" + fieldValue);
}
System.out.println(object);
}catch(Exception e){
e.printStackTrace();
//System.out.println("发生异常");
}finally{
JDBCTools.release(conn, ps, rs);
}
}
在开发中通常使用 Java 中反射机制构造相应对象,利用泛型封装通用的方法。在调用时只需传入相应对象的 Class 、SQL 语句和 SQL参数。
/*
* 1.Object...args:动态参数传入的是一个参数数组,在调用此方法时,传入参数个数不确定,可以不传,可以传一个,可以传多个...
* 2.PreparedStatement.setObject(int index,Object object):为 PreparedStatement 要执行的 sql 设置参数,index 表示第几个位置的参数,object 表示该位置的参数
*/
public <T> T getObject(Class<T> clazz,String sql,Object... args){
T entity = null; //保存结果的对象,因为不知道具体的类,所以用泛型
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JDBCTools.getConnection();
ps = conn.prepareStatement(sql);
for(int i = 0;i < args.length;i++){
ps.setObject(i+1,args[i]);//设置参数,参数的索引从 1 开始
}
rs = ps.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
Map<String, Object> values = new HashMap<>();
if(rs.next()){
for(int i = 0;i < rsmd.getColumnCount();i++){
String columnLabel = rsmd.getColumnLabel(i+1);//获取列名
Object columnObject = rs.getObject(columnLabel);//获取该列的数据
values.put(columnLabel, columnObject);
}
}
//通过反射实例化一个相应的 class 对象实例
entity = clazz.newInstance();
for (Map.Entry<String, Object> entry:values.entrySet()) {
String fieldName = entry.getKey();//属性名
Object fieldValue = entry.getValue();//属性值
//通过反射未属性赋值
Field field = clazz.getDeclaredField(fieldName);//获取相应属性对象
field.set(entity, fieldValue);//为实例化对象的属性设置值
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCTools.release(conn, ps, rs);
}
return entity;
}
调用该方法的代码:
@Test
public void test2(){
String sql = "select cu_name name ,cu_age age from customers where name = ?";
Customers customers = getObject(Customers.class, sql, "ZS");
System.out.println(customers);
String sql2 = "select stu_name name ,stu_age age,stu_major major,tel tel from student where age = ? and tel = ?";
Student student = getObject(Student.class, sql2, "18","15082383272");
System.out.println(student);
}
在开发中有时候不知道数据库表中的字段名,或者字段名与相应类的属性名不一致,这个时候在写 SQL 语句时,就可以为字段名起一个别名,这个别名一定要去相应类的属性名一致,这样后面获取道属性名后,才能赋值成功
使用 ResultSetMetaData 开发示意图:
六、DatabaseMetaData
DatabaseMetaData 是描述数据库的元数据对象,可以由Connection获得
@Test
public void testDatabaseMetaData() {
Connection conn = null;
ResultSet rs = null;
try {
conn = JDBCTools.getConnection();
DatabaseMetaData databaseMetaData = conn.getMetaData();
//可以得到数据库本身的一些信息
//1.得到数据库的版本号
int version = databaseMetaData.getDatabaseMajorVersion();
System.out.println("版本号为:" + version);
//2.得到连接数据库的用户名
String user = databaseMetaData.getUserName();
System.out.println("用户名:" + user);
//3.得到连接数据库的URL
String url = databaseMetaData.getURL();
System.out.println("URL:" + url);
//4.得到所有的数据库
rs = databaseMetaData.getCatalogs();
while(rs.next()){
System.out.println(rs.getString(1));
}
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCTools.release(conn, null, rs);
}
}
七、BeanUtils 工具
1.概述
BeanUtils工具由Apache软件基金组织编写,提供给我们使用,主要解决的问题是:把对象的属性数据封装到对象中。在整个J2EE的编程过程中,我们经常会从各种配置文件中读取相应的数据,需要明白的一点是从配置文件中读取到的数据都是String,但是很显然我们的应用程序中不仅仅有String一种数据类型,比如:基本数据类型(int、double、char、float等),还有自定义数据类型(引用数据类型),那么我们必须面临的一个问题就是讲字符串类型转换为各种具体的数据类型,该怎么办呢?有两种方法供我们是使用:
首先判断需要的数据类型,然后对字符串类型调用相应的方法,将其转换为我们想要的类型
使用BeanUtils工具
对于上面提到的两种方法,我们分析第一种存在的问题是太过于繁琐,每次都要进行大量的类型转换,Apache软件基金会给我们提供了第二种方法,使用其提供的BeanUtils工具,具体的说只需要知道其中的两个方法就能实现类型的转换,很简单,降低了编程的难度。
2.导包
3.常用的方法
- BeanUtils.setProperty(bean, name, value):其中bean是指你将要设置的对象,name指的是将要设置的属性(写成”属性名”),value(要设置的属性的值)
- ConvertUtils.register(Converter converter , ..):当需要将String数据转换成引用数据类型(自定义数据类型时),需要使用此方法实现转换。
- BeanUtils.populate(bean,Map):其中Map中的key必须与目标对象中的属性名相同,否则不能实现拷贝。
- BeanUtils.copyProperties(newObject,oldObject):实现对象的拷贝。
BeanUtils.getProperty(Object, fieldName):获取 Object 的某个属性的值
注意:自定义数据类型使用BeanUtils工具时,本身必须具备getter和setter方法,因为BeanUtils工具本身也是一种内省的实现方法,所以也是借助于底层的getter和setter方法进行转换的。
示例代码:
@Test
public void test2() throws Exception{
Student student = new Student();
BeanUtils.setProperty(student, "name", "张三");
BeanUtils.setProperty(student, "age", "18");
Object val = BeanUtils.getProperty(student, "age");
System.out.println(val);
}
@Test
public void test1() throws Exception{
Student student = new Student();
BeanUtils.setProperty(student, "name", "张三");
BeanUtils.setProperty(student, "age", "18");
System.out.println(student);
}
封装一个查询方法,返回某个对象的结果集:
public <T> List<T> getObjectList(Class<T> clazz,String sql,Object... args){
List<T> list = new ArrayList<>(); //泛型
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JDBCTools.getConnection();
ps = conn.prepareStatement(sql);
for(int i = 0;i < args.length;i++){
ps.setObject(i+1,args[i]); //设置 sql 语句的参数
}
rs = ps.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
List<Map<String, Object>> values = new ArrayList<>();
Map<String, Object> map = null;
while(rs.next()){
map = new HashMap<>();
for(int i = 0;i < rsmd.getColumnCount();i++){
String columnLabel = rsmd.getColumnLabel(i+1);//获取列名
Object columnObject = rs.getObject(columnLabel);//获取该列的数据
map.put(columnLabel, columnObject);
}
values.add(map);
}
T bean = null;
if(values.size() > 0){
//一个 map 就是一个对象
for(Map<String, Object> m:values){
bean = clazz.newInstance();
//获取该对象的所有属性值
for(Map.Entry<String, Object> entry:m.entrySet()){
String propertiName = entry.getKey();
Object propertiValue = entry.getValue();
//设置该对象的所有属性值
BeanUtils.setProperty(bean, propertiName, propertiValue);
}
list.add(bean);
}
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCTools.release(conn, ps, rs);
}
return list;
}
八、向数据库中读取和存储 Blob 类型文件
1. 存储
/*
* 插入 BLOB 类型的数据必须使用 PreparedStatement,因为 BLOB 类型的数据无法使用字符串拼接
*
* 调用 setBlob(int index,InputStream) 方法
*/
@Test
public void testBLOB(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCTools.getConnection();
String sql = "insert into student(name,age,tel,major,picture) values(?,?,?,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "Jack");
ps.setInt(2, 20);
ps.setString(3, "13056505519");
ps.setString(4, "土木工程");
//文件输入流
InputStream is = new FileInputStream(new File("1.jpg"));
ps.setBlob(5, is);
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(conn, ps, rs);
}
}
}
2. 读取
/*
* 读取数据库里面的 Blob 类型文件:
* 1.获取该文件的 Blob 对象 Blob blob = rs.getBlob(3);
* 2.从该 Blob 对象中获取此文件的输入流对象
* 3.通过输入流和输出流,输出该文件
*/
@Test
public void readBlob(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCTools.getConnection();
String sql = "select name,age,picture from student where id = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, 8);
rs = ps.executeQuery();
while(rs.next()){
String name = rs.getString(1);
String age = rs.getString(2);
//获取此文件的 Blob 对象
Blob blob = rs.getBlob(3);
//获取此文件的输入流对象
InputStream is = blob.getBinaryStream();
OutputStream os = new FileOutputStream(new File("Jack.jpg"));
byte[] b = new byte[1024];
int len;
while((len = is.read(b)) != -1){
os.write(b, 0, len);
}
os.close();
is.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(conn, ps, rs);
}
}
九、获取插入记录的主键参数
- 创建 PreparedStatement 对象时选择 PreparedStatement(String sql,int autoGeneratedKeys) 构造函数
- 第二个参数传入 Statement.RETURN_GENERATED_KEYS
- 执行 SQL 语句后,用 getGeneratedKeys() 方法获取主键的结果集
- 结果集中的第一列数据就是主键的值
示例代码:
@Test
public void testGetKeyValue(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCTools.getConnection();
String sql = "insert into student(name,age,tel,major) values(?,?,?,?)";
ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, "Jack");
ps.setInt(2, 20);
ps.setString(3, "13056505519");
ps.setString(4, "土木工程");
ps.executeUpdate();
//通过 getGeneratedKeys() 获取包含了新生成的主键的 ResultSet 对象
rs = ps.getGeneratedKeys();
if(rs.next()){
System.out.println(rs.getString(1));
}
//ResultSet 里面只有一列 GENERATED_KEY,用于存放新生成的主键值
ResultSetMetaData rsmd = rs.getMetaData();
for(int i = 0;i < rsmd.getColumnCount();i++){
System.out.println(rsmd.getColumnName(i+1));
}
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(conn, ps, rs);
}
}
十、事务操作
基本步骤:
1. conn.setAutoCommit(false):设置事务为不自动提交
2. 操作全部成功完成,提交事务:conn.commit()
3. 发生异常,回滚事务:conn.rollback()
示例代码:
@Test
public void test(){
Connection conn = null;
DAO dao = new DAO();
try {
conn = JDBCTools.getConnection();
//开启事务,设置不自动提交
conn.setAutoCommit(false);//这一步必须要有,很重要
String sql = "update customers set balance = balance - 5000 where id = 1";
dao.updata(conn, sql);
//制造异常
int i = 10/0;
sql = "update customers set balance = balance + 5000 where id = 2";
dao.updata(conn, sql);
//提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
//发生异常,执行回滚
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally{
JDBCTools.release(conn, null, null);
}
}
十一、数据库连接池
一. DBCP 数据库连接池
步骤:
1. 导包(2个),依赖于 Commons Pool
2.创建数据库连接池
3.设置常用属性
4.获取连接
直接使用
@Test
public void testDBCP() throws Exception{
BasicDataSource dataSource = null;
//创建 DBCP 示例
dataSource = new BasicDataSource();
//为数据源设置必须的属性
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
//为数据源设置可选的属性
//(1).指定数据库连接池中初始化连接数的个数
dataSource.setInitialSize(10);
//(2).设置连接池中最大连接数:同一时刻可以同时向数据库申请的连接数
dataSource.setMaxIdle(50);
//(3).设置连接池的最小连接数:在数据库连接池中保存的最少的空闲的连接数量
dataSource.setMinIdle(5);
//(4).等待数据库连接池分配连接的最长时间,单位为毫秒,超出规定时间将抛出异常
dataSource.setMaxWaitMillis(1000 * 5);
//从数据源中获取连接
Connection conn = dataSource.getConnection();
System.out.println(conn.getClass());
}
- 从配置文件中加载数据库连接信息
@Test
public void testDataSource() throws Exception{
InputStream in = JDBCTools.class.getClassLoader().getResourceAsStream("dbcp.properties");
Properties properties = new Properties();
properties.load(in);
DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
System.out.println(dataSource.getConnection());
BasicDataSource basicDataSource = (BasicDataSource) dataSource;
System.out.println(basicDataSource.getMaxWaitMillis());
System.out.println(basicDataSource.getMaxIdle());
}
配置文件 dbcp.properties :
username = root
password = 123456
url = jdbc:mysql://localhost:3306/test
driverClassName = com.mysql.jdbc.Driver
initialSize = 10
maxIdle = 50
minIdle = 5
maxWaitMillis = 5000
二. C3P0 数据库连接池
步骤:
- 导包:1).c3p0-0.9.5.2.jar , 2).mchange-commons-java-0.2.11.jar
- 创建 c3p0-config.xml 文件
- 创建 ComboPooledDataSource 实例:DataSource dataSource = new ComboPooledDataSource(“helloC3p0”);
- 从DataSource 中获取数据库连接
直接使用:
@Test
public void testC3P0() throws Exception{
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setUser("root");
cpds.setPassword("123456");
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
System.out.println(cpds.getConnection());
}
- 从配置文件中加载数据库连接信息:
@Test
public void testC3P0XML() throws Exception{
DataSource dataSource = new ComboPooledDataSource("helloC3p0");//注意配置文件里面的名称
System.out.println(dataSource.getConnection());
ComboPooledDataSource cpds = (ComboPooledDataSource) dataSource;
System.out.println(cpds.getMaxPoolSize());
}
C3P0.config.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!--注意这里的名称 -->
<named-config name="helloC3p0">
<!-- 指定连接数据源的基本属性 -->
<property name="user">root</property>
<property name="password">123456</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- 在 XML 文件中所有的连接符号 '&' 都要用 ''&' 代替 -->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&
useUnicode=true&characterEncoding=UTF-8</property>
<!-- 若数据库中连接数不足时, 一次向数据库服务器申请多少个连接 -->
<property name="acquireIncrement">5</property>
<!-- 初始化数据库连接池时连接的数量 -->
<property name="initialPoolSize">5</property>
<!-- 数据库连接池中的最小的数据库连接数 -->
<property name="minPoolSize">5</property>
<!-- 数据库连接池中的最大的数据库连接数 -->
<property name="maxPoolSize">10</property>
<!-- C3P0 数据库连接池可以维护的 Statement 的个数 -->
<property name="maxStatements">20</property>
<!-- 每个连接同时可以使用的 Statement 对象的个数 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
三、DBCP 和 C3P0 的区别:
- dbcp没有自动的去回收空闲连接的功能 c3p0有自动回收空闲连接功能。
- 两者主要是对数据连接的处理方式不同!C3P0提供最大空闲时间,DBCP提供最大连接数。
- C3P0 当连接超过最大空闲连接时间时,当前连接就会被断掉。DBCP 当连接数超过最大连接数时,所有连接都会被断开。
- 从执行速度上看 DBCP 效率更高,但是 C3P0 更稳定。
十二、DBUtils 工具类
QueryRunner 类:
通过 QueryRunner 类可以执行 sql 语句。
/*
* QueryRunner 的 query 方法的返回值取决于其 ResultSetHandler 参数的 handler 方法的返回值
*/
public void testQuery(){
QueryRunner queryRunner = new QueryRunner();
String sql = "select name,age,tel,major from student";
Connection conn = null;
try {
conn = JDBCTools.getConnection();
@SuppressWarnings("unchecked")
Object obj = queryRunner.query(conn, sql, new MyResultSetHandler());
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(conn, null, null);
}
}
class MyResultSetHandler implements ResultSetHandler{
List<Student> list = new ArrayList<>();
@Override
public Object handle(ResultSet rs) throws SQLException {
while(rs.next()){
String name = rs.getString(1);
int age = rs.getInt(2);
String tel = rs.getString(3);
String major = rs.getString(4);
Student stu = new Student(name, age, tel, major);
list.add(stu);
}
return list;
}
}
BeanHandler:
将结果集的第一条记录转为创建 BeanHandler 的对象传入的 class 参数对应的对象。
public void testBeanHandler(){
Connection conn = null;
QueryRunner queryRunner = new QueryRunner();
try {
conn = JDBCTools.getConnection();
String sql = "select name,age,tel,major from student where id = ?";
Student stu = (Student) queryRunner.query(conn, sql, new BeanHandler(Student.class),4);
System.out.println(stu);
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(conn, null, null);;
}
}
BeanListHandler:
将结果集封装成参数 Class 对应的对象集合list。(list 不为 null ,但可能为空集合(size == 0))
public void test (){
Connection conn = null;
QueryRunner queryRunner = new QueryRunner();
try {
conn = JDBCTools.getConnection();
String sql = "select name,age,tel,major from student where id >= ?";
List<Student> list = (List<Student>) queryRunner.query(conn, sql, new BeanListHandler(Student.class),4);
for (Student student : list) {
System.out.println(student);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(conn, null, null);;
}
}
MapHandler:
MapHandler:返回 SQL 对应的第一条记录对应的 Map 对象; 键:SQL 查询的列名(不是别名), 值:列的值。
public void testMapHandler(){
Connection conn = null;
QueryRunner queryRunner = new QueryRunner();
try {
conn = JDBCTools.getConnection();
String sql = "select name,age,tel,major from student where id >= ?";
Map<String, Object> map = queryRunner.query(conn, sql, new MapHandler(),1);
System.out.println(map);
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(conn, null, null);;
}
}
MapListHandler:
MapListHandler:将结果集转化为一个 Map 的 List。
Map 对应一条查询记录:键:SQL 查询的列名(不是别名), 值:列的值。
public void testMapListHandler(){
Connection conn = null;
QueryRunner queryRunner = new QueryRunner();
try {
conn = JDBCTools.getConnection();
String sql = "select name,age,tel,major from student where id >= ?";
List<Map<String, Object>> list = (List<Map<String, Object>>) queryRunner.query(conn, sql, new MapListHandler(),1);
for (Map<String, Object> map : list) {
System.out.println(map);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(conn, null, null);;
}
}
ScalarHandler:
将结果集转化为一个值(可以使任意的基本数据类型和字符串、Date 等)返回; 默认是返回第一列数据。
public void testScalarHandler(){
Connection conn = null;
QueryRunner queryRunner = new QueryRunner();
try {
conn = JDBCTools.getConnection();
String sql = "select name from student where id >= ?";
Object result = queryRunner.query(conn, sql, new ScalarHandler(),1);
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(conn, null, null);;
}
}
十三、DAO 设计模式
程序分层:
DAO 组成:
- DatabaseConnection:专门负责数据库打开与关闭操作的类
- VO 类:负责数据的传输和包装,每一个VO类的对象都表示表中的每一条记录;必须包含setter, getter方法和无参构造器(才能利用反射机制)。;
- DAO:主要定义操作的接口,定义一系列数据库的原子性操作,例如增删改查等;
- mpl: DAO接口的真实实现类,主要完成具体数据库操作,但不负责数据库的打开和关闭;
- Proxy:代理实现类,主要完成数据库的打开和关闭并且调用真实实现类对象的操作;
- Factory: 工厂类,通过工厂类取得一个DAO的实例化对象。
对于包的命名:
在使用DAO时对包有严格的命名
- DAO接口: xxx.dao.IXxxDAO
- DAO接口真实实现类:xxx.dao.impl.XxxDAOImpl
- DAO接口代理实现类:xxx.dao.proxy.XxxDAOProxy
- VO类: xxx.vo.Xxx, VO命名要与表的命名一致
- 工厂类:xxx.factory.DAOFactory.
示例代码:
VO 类:Student
public class Student {
public String name;
public int age;
public String tel;
public String major;
public Student() {
super();
// TODO Auto-generated constructor stub
}
public Student(String name, int age, String tel, String major) {
super();
this.name = name;
this.age = age;
this.tel = tel;
this.major = major;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getMajor() {
return major;
}
public void setMajor(String major) {
this.major = major;
}
@Override
public String toString() {
return "Student : name=" + name + ", age=" + age + ", tel=" + tel + ", major=" + major + "";
}
}
DAO:定义泛型的方法接口
public interface DAO<T> {
/**
*
* @Title: update
* @Description: TODO(用于操作 INSERT、UPDATE、DELETE 语句)
* @param @param conn:数据库连接
* @param @param sql:要执行的 sql 语句
* @param @param args:填充占位符的参数
* @throws SQLException
*/
void update(Connection conn,String sql,Object... args) throws SQLException;
/**
*
* @Title: getObject
* @Description: TODO(获取一个对象的方法)
* @param @param conn
* @param @param sql
* @param @param args
* @param @return 参数
* @throws
*/
T getObject(Connection conn,String sql,Object... args) throws SQLException;
/**
*
* @Title: getObjectList
* @Description: TODO(获取一组对象的集合)
* @param @param conn
* @param @param sql
* @param @param args
* @param @return 参数
* @throws
*/
List<T> getObjectList(Connection conn,String sql,Object... args) throws SQLException;
/**
*
* @Title: getForValue
* @Description: TODO(返回一个具体的值,比如姓名、工资、年龄等)
* @param @param connection
* @param @param sql
* @param @param args
*/
<E> E getValue(Connection connection,String sql, Object ... args) throws SQLException;
}
JdbcDaoImpl 类:泛型的通过实现类,代理类只需继承该类即可。
/**
* DAO 的具体实现类,主要实现 接口中的各个方法,而不需要管理数据库连接和关闭等操作。
*/
public class JdbcDaoImpl<T> implements DAO<T> {
private QueryRunner queryRunner = null;
private Class<T> type;
public JdbcDaoImpl() {
queryRunner = new QueryRunner();
//通过反射, 获得 Class 定义中声明的父类的泛型参数类型
type = ReflectionUtils.getSuperGenericType(getClass());
}
@Override
public void update(Connection conn, String sql, Object... args) throws SQLException {
queryRunner.update(conn, sql, args);
}
@Override
public T getObject(Connection conn, String sql, Object... args) throws SQLException {
T entity = queryRunner.query(conn, sql, new BeanHandler<T>(type), args);
return entity;
}
@Override
public List<T> getObjectList(Connection conn, String sql, Object... args) throws SQLException {
return queryRunner.query(conn, sql, new BeanListHandler<>(type), args);
}
@Override
public <E> E getValue(Connection connection, String sql, Object... args) throws SQLException {
// TODO Auto-generated method stub
return null;
}
}
代理类 StudentDAOProxy :
/**
* 代理类 : 所有的方法实体已交由实现类 JdbcDaoImpl 完成,代理类中完成数据库的连接和关闭以及调用实现类中相应的方法;或者按照需求添加相应的方法
*/
public class StudentDAOProxy extends JdbcDaoImpl<Student>{
private Connection conn = null;
public StudentDAOProxy(){
try {
this.conn = JDBCTools.getConnection(); //实例化
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*
* @Description: TODO(调用实现类中对象的方法实体,实现相应的操作)
* @param @param sql
* @param @param args
* @throws
*/
public List<Student> getStudentList(String sql,Object... args){
List<Student> list = null;
try {
//调用实现类的对应方法实体
list = this.getObjectList(conn, sql, args);
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCTools.release(conn, null, null);
}
return list;
}
}
工厂类:DAOFactory
/**
* @Description DAO 工厂类,获取 DAO 实例
* @version 1.0
* @since JDK 1.6.0_21
* 文件名称:DAOFactory.java
* 类说明:
*/
public class DAOFactory {
public static StudentDAOProxy getStudentDAOProxyInstance(){
return new StudentDAOProxy();
}
public static CustomersDAO getCustomersDAOInstance(){
return new CustomersDAO();
}
}
在需要某个代理类时,只需调用工具类中的相应方法获取。