我们在上一篇文章中完成了操作数据库之通用的增删改操作,现在这篇文章我们来实现查询数据库的操作。
以customers表为案例
查询操作举例
查询数据库的操作相对于增删改操作多了一个结果集的返回,因此我们在程序中不能直接使用execute()方法。首先我要以我的数据库中的customer表为例子来写一个查询。这次我们使用ResultSet类来接收我们的结果集。
public class CustomerForQuery {
@Test
public void testQuery() {
Connection conn=null;
PreparedStatement ps=null;
ResultSet resultSet=null;
try {
conn = jdbcutils.getConnection();
String sql="select id,name,email,birth from customers where id =?";
ps = conn.prepareStatement(sql);
ps.setObject(1, 1);
//执行并返回结果集
resultSet = ps.executeQuery();
//处理结果集
if(resultSet.next()) {//判断结果集的吓一条是否有数据,如果有则返true并指针下移
//获取当前数据的各个字段值
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String email = resultSet.getString(3);
Date birth = resultSet.getDate(4);
//方式一:直接显示
//System.out.println("id="+id+" name="+name+" email="+email+" birth="+birth);
//方式二:封装到一个数组中
//Object[] data=new Object[] {id,name,email,birth};
//方式三:将数据封装成一个对象
Customer customer=new Customer(id,name,email,birth);
System.out.println(customer);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
jdbcutils.closeResource(conn, ps, resultSet);
}
}
}
显示我们的数据可以直接显式地输出,也可以封装到一个数组中。当然,我选择使用一个类来专门封装。坚持一个数据表对应一个Java类,表中一个记录对应一个Java类的对象,表中的一个字段对应Java类的一个属性。因此我选择创建一个Customer类:
public class Customer {
private int id;
private String name;
private String email;
private Date birth;
public Customer() {
super();
// TODO Auto-generated constructor stub
}
public Customer(int id, String name, String email, Date birth) {
super();
this.id = id;
this.name = name;
this.email = email;
this.birth = birth;
}
@Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + ", email=" + email + ", birth=" + birth + ", getClass()="
+ getClass() + ", hashCode()=" + hashCode() + ", toString()=" + super.toString() + "]";
}
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 String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
}
这就是以上代码中方式三Customer类的来源。
通用查询操作(针对customers表)
想要实现对customers表的通用查询操作,我们需要解决查询数据数目未知的问题。首先解决占位符的数量,可以使用 Object ...arges来接受sql语句,通过获取args的长度我们可以得到需要填充的占位符的数量。那结果集的数据种类数量呢?这就需要引入ResultSetMetaData类了,我们可以通过它的包装方法完成获取到结果集中列的数量和列的名字等操作。最后通过反射来获取结果集中指定名字的数据,考虑到数据可能是私有的,就先将其通过方法setAccessible()方法允许访问,最后将该值赋值给Customer对象。以下是代码:
public Customer queryForCustomers(String sql,Object ...arges) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
conn = jdbcutils.getConnection();
ps = conn.prepareStatement(sql);
for(int i=0;i<arges.length;i++) {
ps.setObject(i+1, arges[i]);
}
rs = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
//通过ResultSetMetData获取结果集中的数据
int columnCount = rsmd.getColumnCount();
while(rs.next()) {
Customer cust=new Customer();
//处理结果集一行数据中的每一个列
for(int i=0;i<columnCount;i++) {
Object columnValue = rs.getObject(i+1);
//获取每个列的列名
String columnName = rsmd.getColumnName(i+1);
//需要给cust某个属性赋值value,通过反射
Field field = Customer.class.getDeclaredField(columnName);
field.setAccessible(true);
field.set(cust, columnValue);
}
return cust;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
jdbcutils.closeResource(conn, ps, rs);
}
return null;
}
有以下成功测试代码:
@Test
public void testQueryForCustomer() {
String sql="select id,name,birth,email from customers where id =?";
String sql2="select name,birth,email from customers where name= ?";
Customer customer = queryForCustomers(sql2, "王菲");
System.out.println(customer);
}
以Order表为案例
查询操作举例
同上,有代码
@Test
public void testQuery1() {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
conn = jdbcutils.getConnection();
String sql="select order_id,order_name,order_date from `order` where order_id=?";
ps = conn.prepareStatement(sql);
ps.setObject(1, 1);
rs = ps.executeQuery();
if(rs.next()) {
int id = (int)rs.getObject(1);
String name=(String) rs.getObject(2);
Date date=(Date) rs.getObject(3);
Order order = new Order(id,name,date);
System.out.println(order);
}
}catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
jdbcutils.closeResource(conn, ps, rs);
}
}
成功运行,没什么好说的。
通用查询操作(针对Order表)
接下来,有了customers表通用查询的例子在前,我们可以写出代码
//通用的针对于Order表的操作
public Order OrderForQuery(String sql,Object ...args){
Connection conn=null;
PreparedStatement ps=null;
//执行获取结果集
ResultSet rs=null;
try {
conn = jdbcutils.getConnection();
ps = conn.prepareStatement(sql);
for(int i=0;i<args.length;i++) {
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
//获取列数
int columnCount = rsmd.getColumnCount();
if(rs.next()) {
Order order=new Order();
for(int i=0;i<columnCount;i++) {
//获取每一个列的列值
Object columnValue = rs.getObject(i+1);
//获取列的列名,需要获取类的别名
//String columnName = rsmd.getColumnName(i+1);(不推荐使用)
String columnName=rsmd.getColumnLabel(i+1);
//通过反射将对象指定名的属性值赋值为指定的值
Field field = Order.class.getDeclaredField(columnName);
field.setAccessible(true);
field.set(order, columnValue);
}
return order;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
jdbcutils.closeResource(conn, ps, rs);
}
return null;
}
同样的有Order类来接受结果集:
public class Order {
private int orderid;
private String ordername;
private Date orderdate;
public Order() {
super();
}
public Order(int orderid, String ordername, Date orderdate) {
super();
this.orderid = orderid;
this.ordername = ordername;
this.orderdate = orderdate;
}
@Override
public String toString() {
return "Order [orderid=" + orderid + ", ordername=" + ordername + ", orderdate=" + orderdate + "]";
}
public int getOrderid() {
return orderid;
}
public void setOrderid(int orderid) {
this.orderid = orderid;
}
public String getOrdername() {
return ordername;
}
public void setOrdername(String ordername) {
this.ordername = ordername;
}
public Date getOrderdate() {
return orderdate;
}
public void setOrderdate(Date orderdate) {
this.orderdate = orderdate;
}
}
需要注意的是,order表的通用查询和customer的有些细节上的不同。我将获取列的列名的代码
String columnName = rsmd.getColumnName(i+1);
替换成了
String columnName=rsmd.getColumnLabel(i+1);
具体原因是表的字段名和我的类的属性名不相同,这是在书写customers表的通用查询方法中没有遇到的情况。我选择使用getColumnLable()方法来替换老方法,并且在sql语句中给列名取别名(取的别名与类属性名相同),这样该方法就可以识别到类的别名。不替换老方法程序会执行报错java.lang.NoSuchFieldException,意思是查无此名。而getColumnLable()在得知sql中没有给字段取别名时,它就会获取字段的列名,这也是我建议替换掉原来方法的原因。
通用表查询
有了前面两个表的铺垫,我们可以开始尝试写一个通用表的查询代码了。现在摆在面前的有一个问题,在前面的两个表的代码书写中,我们都是根据表的具体情况直接创建表的类和对象,现在我们没办法这么干了,因为我们无法确定调用该方法需要针对哪一个表来操作。解决办法就是运用泛型的知识,在方法中传入所需要的表的类型,当然在必须是已经实现创建了对应的类。
public <T> List<T> getForList(Class<T> clazz,String sql,Object ...args){
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
conn = jdbcutils.getConnection();
ps = conn.prepareStatement(sql);
for(int i=0;i<args.length;i++) {
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
//通过ResultSetMetData获取结果集中的数据
int columnCount = rsmd.getColumnCount();
//创建集合对象
ArrayList<T> list=new ArrayList<>();
while(rs.next()) {
T t = clazz.newInstance();
//处理结果集一行数据中的每一个列:给t对象指定的属性赋值
for(int i=0;i<columnCount;i++) {
Object columnValue = rs.getObject(i+1);
//获取每个列的列名
String columnName = rsmd.getColumnLabel(i+1);
//需要给cust某个属性赋值value,通过反射
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnValue);
}
list.add(t);
}
return list;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
jdbcutils.closeResource(conn, ps, rs);
}
return null;
}
如果仔细观察了代码的话,就会发现我使用了List集合来接收多个对象,因为我们在实际运用过程中肯定不止需要一条数据,肯定是对多个数据进行操作,而数组不适合应用当前场合,所以我使用了List集合。
有如下测试代码,运行成功。
@Test
public void testGetForList() {
String sql="select id,name,email from customers where id<?";
List<Customer> list=getForList(Customer.class, sql, 12);
list.forEach(System.out::println);
}
其实大部分的操作已经在前两个案例中完成了,第三次的通用表查询更多的是一种总结概括,将确定的转变成可自定义的,都是细节上的改动。