最近在学习JDBC的基础部分,刚好只把基础的增删改查写完了,拿来和大家一起分享,有问题的地方多多指教,同时也相当于对这一段时间的总结吧(菜鸟一枚)。
为了提高我们代码的复用性,我们将一些信息写在配置文件中,如果我们以后需要查询不同的数据库时,只需要对配置文件中url进行修改即可。
因为我们在使用JDBC连接数据库的时候,获取连接的方式基本上都是一样的,所以我们也将其封装为一个类,并且所有的使用的对象,我们在对数据库进行增删改查以后,都要关闭资源,所以我们也将关闭资源的方法,写在这个类下边。
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
public class JDBCutil {
/*
获取链接,工具类都是静态方法 :连接数据库
*/
public static Connection getConnection() throws Exception{
/*创建io流对象,读取配置文件中的信息 */
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("JDBC.properties");
Properties pro = new Properties();
pro.load(is);
String user = pro.getProperty("user");
String url = pro.getProperty("url");
String password = pro.getProperty("password");
String driverClass = pro.getProperty("driverClass");
// 创建驱动
Class.forName(driverClass);
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
// 关闭资源
public static void closeResource(Connection conn, PreparedStatement pre) {
try {
if (conn != null) conn.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
if (pre != null) pre.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void closeResource(Connection conn, PreparedStatement pre, ResultSet res) {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (pre != null) {
pre.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (res != null) {
res.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
DriverManager类:
用于实现数据库的连接,即实现创建驱动和创建数据库的连接对象。
解释一下这里为什么写两个关闭资源的方法,因为对数据库的增删改查其实可以分为两类,一类是增删改,一类是查,二者的区别在于查的这个过程,我们会产生一个数据集,就相当于我们在mysql中写查询语句的时候一样,会出现一张表,所以我们在Java中要对查询到的结果进行处理,而前者呢,只是对数据库表内信息的修改,修改成功以后,我们是看不到的,只能通过查询语句来验证。所以写了两个关闭资源的方法来针对两种类别的修改。
那么接下来就让我正式开始写增删改查的代码了,呜呼!!
/* 先来看一个基础版本的添加数据*/
public static void insert() {
Connection con = null;
PreparedStatement pre = null;
try {
con = JDBCutil.getConnection();
// 预编译sql语句,返回prepareStatement实例对象
String sql = "INSERT INTO student (s_no,class_no,s_name,s_sex,s_birthday) values (?,?,?,?,?)"; // ? 代表占位符
pre = con.prepareStatement(sql);
// 填充占位符
pre.setString(1, "12000");
pre.setString(2, "13007");
pre.setString(3, "张三");
pre.setString(4, "男");
//日期类的数据类型要解析
SimpleDateFormat simp = new SimpleDateFormat("yyyy-MM-dd");
Date date = simp.parse("1999-12-12");
pre.setDate(5, new java.sql.Date(date.getTime()));
// 执行sql语句
pre.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCutil.closeResource(con,pre);
}
}
Connection接口:从DriverManager对象中获取对象,用于表示与数据库的连接对象
preparedStatement 对象PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句, 它的setXXX()方法,主要是用来设置我们占位符所在位置的内容,其中第一个形参表示下标,记住: 从1开始,第二个参数是占位符所在位置的内容,记住要一一对应。从Connection对象中获取。
其实有很多可以改进的地方,当然我们后面会有。
下一个是修改语句:
/*也是一个基础版本的修改*/
// 修改student表的一条记录
public static void update() {
// 1,获取数据库的连接
Connection connection = null;
PreparedStatement prep = null;
try {
connection = JDBCutil.getConnection();
// 2,预编译数据库语句,返回preparedStatement实例
String sql = "update student set s_name = ? where s_no = ? ";
prep = connection.prepareStatement(sql);
// 3,填充占位符
prep.setString(1, "李四");
prep.setString(2, "12000");
// 4,执行
prep.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5,关闭资源
JDBCutil.closeResource(connection, prep);
}
}
其实学的差不多了,感觉基本套路都是一样的。
下一个看看删除:
// 删除数据
public static void delete() {
Connection con = null;
PreparedStatement pre = null;
try {
con = JDBCutil.getConnection();
// 预编译sql语句,
String sql = " delete from student where s_name = ? and s_no = ? ";
// 填充占位符
PreparedStatement prep = con.prepareStatement(sql);
prep.setObject(1, "李四");
prep.setObject(2, 12000);
prep.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCutil.closeResource(con, pre);
}
}
接下来看看一个基础版本的查询方法,后面我会多给出几种查询以及对前面的增删改代码的修改,让他们更加使用于不同的场景。
// 查询数据
public static void search() {
Connection conn = null ;
PreparedStatement pre = null;
try {
conn = JDBCutil.getConnection();
String sql = "select * from student";
PreparedStatement prep = conn.prepareStatement(sql);
ResultSet result = prep.executeQuery();
while (result.next()) {
String s_no = result.getString("s_no");
String class_no = result.getString("class_no");
String s_name = result.getString("s_name");
String s_sex = result.getString("s_sex");
String s_birthday = result.getString("s_birthday");
System.out.println("s_no " + s_no + " class_no " + class_no + " s_name " + s_name + " s_sex " + s_sex + " s_birthday " + s_birthday);
}
} catch (Exception e ) {
e.printStackTrace();
} finally {
JDBCutil.closeResource(conn,pre);
}
}
ResultSet接口:
用于表示查询以后的结果集,当prepareStatement对象调用查询操作的时候会返回该接口的对象。
然后我们在对结果集进行解析的时候,使用ResultSet对象的getXXX()方法,获取对应数据库表中的字段名,并且进行一个输出,就会得到我们想要查询的结果。
接下来我会给大家提供一个删除或者修改或者增加语句的改进方式
/*给大家来个删除操作的改进吧,其他的两种基本上一致,希望大家自己琢磨*/
import java.sql.Connection;
import java.sql.PreparedStatement;
public class update {
public static void main(String[] args) {
String sql = "delete from student where s_name = ? ";
update p = new update();
p.updataDatabase(sql,"xiao");
}
public void updataDatabase(String sql, Object ...args) {
Connection conn = null;
PreparedStatement pre = null;
try {
conn = JDBCutil.getConnection();
pre = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pre.setObject(i + 1, args[i]);
}
pre.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCutil.closeResource(conn,pre);
}
}
}
提示: Object ... args :我们可以看成一个数组,传参的时候,传入几个,args.length就是几
setObject() : 填充占位符,记住下标从1开始,并且由于我们不知道sql语句中的占位符有几个,所以选择使用循环进行填充。为什么使用setObject呢??因为我们也无法知道占位符所在的数据的数据类型啊!!!
其他的增加语句和修改语句都是这样的进行操作
接下来就是最难处理的查询了:
首先看看对一条结果集的处理方式:
这是咱们的学生表:
提示一下,我们设计的数据库中肯定不止一张表,我们在进行查询操作的时候尽量将每张表都封装进一个类中。
我封装的学生类:
import java.util.Date;
public class studentTest {
private String sNo;
private String sName;
private String classNo;
private Date sBirthday;
public studentTest() {
}
public studentTest(String sNo, String sName, String classNo, Date sBirthday) {
this.sNo = sNo;
this.sName = sName;
this.classNo = classNo;
this.sBirthday = sBirthday;
}
public String getsNo() {
return sNo;
}
public void setsNo(String sNo) {
this.sNo = sNo;
}
public String getsName() {
return sName;
}
public void setsName(String sName) {
this.sName = sName;
}
public String getClassNo() {
return classNo;
}
public void setClassNo(String classNo) {
this.classNo = classNo;
}
public Date getsBirthday() {
return sBirthday;
}
public void setsBirthday(Date sBirthday) {
this.sBirthday = sBirthday;
}
@Override
public String toString() {
return "studentTest{" +
"sNo='" + sNo + '\'' +
", sName='" + sName + '\'' +
", classNo='" + classNo + '\'' +
", sBirthday='" + sBirthday + '\'' +
'}';
}
}
我们对王蕾的信息进行查询:
public void studentTest() {
Connection conn = null;
PreparedStatement pre = null;
ResultSet resultSet = null;
try {
conn = JDBCutil.getConnection();
String sql = "select s_no sNo,s_name sName,class_no classNo,s_birthday sBirthday from student where s_name = ?";
pre = conn.prepareStatement(sql);
pre.setString(1, "王蕾");
resultSet = pre.executeQuery();
//ResultSetMetaData 有关 ResultSet 中列的名称和类型的信息。
ResultSetMetaData meta = resultSet.getMetaData();
// 获取列数
int columnCount = meta.getColumnCount();
if (resultSet.next()) {
studentTest s = new studentTest();
for (int i = 0; i < columnCount; i++) {
// 获取列值
Object columValue = resultSet.getObject(i + 1);
// 获取列名,这里因为我封装的类中的属性和数据库中对不上,所以使用别名,获取的也是别名
String columnLabelnName = meta.getColumnLabel(i + 1);
// getDeclaredFiled() 能获取类本身的属性成员(私有,公共,保护),这里返回一个Field类对象,通过对象进行操作
Field declaredField = s.getClass().getDeclaredField(columnLabelnName);
// 取消java语言访问检查,能够提高反射的速度
declaredField.setAccessible(true);
//通过反射给指定的字段赋值
declaredField.set(s, columValue);
}
System.out.println(s);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCutil.closeResource(conn, pre, resultSet);
}
}
}
这里可能注释有点多,我慢慢来解释,
ResultSetMetaData 有关 ResultSet 中列的名称和类型的信息,我们可以通过此对象来找到数据表中列名信息。
提示:数据库中一般命名都是s_name 这种有下划线的,而java中命名一般都是驼峰命名法,所以我们在写jdbc代码的时候,sql语句进行使用别名,避免通过反射找不到与之对应的类的属性。
接下来给大家介绍一种针对不同的表的通用查询方式:
/*
针对不同的表的通用查询操作
*/
public <T> T testDifTable(Class<T> clazz, String sql, Object... args) throws Exception {
Connection conn = JDBCutil.getConnection();
PreparedStatement pre = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pre.setObject(i + 1, args[i]);
}
ResultSet resultSet = pre.executeQuery();
ResultSetMetaData metaData = pre.getMetaData();
int columnCount = metaData.getColumnCount();
if (resultSet.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
Object clomnValue = resultSet.getObject(i + 1);
String columnName = metaData.getColumnName(i + 1);
Field declaredField = t.getClass().getDeclaredField(columnName);
declaredField.setAccessible(true);
declaredField.set(t, clomnValue);
}
JDBCutil.closeResource(conn, pre, resultSet);
return t;
}
return null;
}
/*再给大家几个测试用例:*/
@Test
public void test() throws Exception {
// String sql = "select s_no from student where s_name = ?";
// student clazz = new student();
// student s = testDifTable(clazz.getClass(), sql, "王蕾");
// System.out.println(s);
String sql1 = "select s_no,s_name from student where s_name = ?";
student s1 = testDifTable(new student().getClass(), sql1, "王蕾");
System.out.println(s1);
}
clazz.newInstence: 用于创建此Clazz对象表示的类的新实例,它是一种非静态的方法,只能通过类对象访问。
其实大多数的代码我们自己动手写一遍以后,大概的思路基本上就清晰了,我们可以先不着急往后学,停下来,做做总结,加油