java为连接不同的数据库提供了统一的接口,这样每个数据库厂商需要提供针对这个接口的具体实现,称之为数据库驱动。而由于接口是一种规范,因此就算对于不同的数据库,对他们的操作也是相同的,例如使用Driver driver = new 具体数据库驱动();就能得到特定数据库的驱动对象,然后通过driver.connect(参数1, 参数2)即可获取数据库连接。不管是什么数据库,对他们操作都是这些代码,大大提升了开发效率。
在这之前,要获取mysql的驱动,你要把相应的jar包放在项目下:
举个例子来理解上述操作,比如你要去旅游,这个Driver就相当于车,url就相当于目的地,properties就相当于你的车钥匙,只有这些都准备好了才能出发,这便是connection。
上述方式出现了第三方类:
Driver driver = new com.mysql.jdbc.Driver();为了有更好的可移植性,我们可以用反射的方式,这样就可以动态的创建不同数据库的对象:
// 方式2 对方式1的迭代 @Test public void test2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException { Class clazz = Class.forName("com.mysql.jdbc.Driver"); Driver driver = (Driver) clazz.newInstance(); String url = "jdbc:mysql://localhost:3306/test"; Properties info = new Properties(); info.setProperty("user", "root"); info.setProperty("password", "root"); Connection connection = driver.connect(url, info); System.out.println(connection); }为什么注释掉那两行也能成功?看下图:
因为当反射把Driver类加载到内存中时,就会自动执行类中的静态代码块,自动的帮我们new了Driver对象。
将连接数据库需要的配置信息(url,user啥的)写到文件中,通过读文件的方式连接。
@Test public void test5() throws Exception { // 默认识别路径是src下 InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties"); Properties properties = new Properties(); properties.load(is); String user = properties.getProperty("user"); String password = properties.getProperty("password"); String url = properties.getProperty("url"); String driverClass = properties.getProperty("driverClass"); Class.forName(driverClass); Connection connection = DriverManager.getConnection(url, user, password); System.out.println(connection); }
这个方式存在如下弊端:
需要拼接字符串sql注入问题https://www.bilibili.com/video/BV1eJ411c7rf?p=12
关于拼串: 关于sql注入:
我们可以将创建连接/关闭连接这种代码封装到一个工具类中,以便其他的类调用:
package utils; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Properties; public class JDBCUtils { public static Connection getConnection() throws Exception{ // InputStream stream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"); InputStream stream = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties properties = new Properties(); properties.load(stream); String user = properties.getProperty("user"); String password = properties.getProperty("password"); String url = properties.getProperty("url"); String driverClass = properties.getProperty("driverClass"); Class.forName(driverClass); Connection connection = DriverManager.getConnection(url, password, user); return connection; } public static void closeResource(Connection conn, PreparedStatement ps) { try { if (conn!=null) conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } try { ps.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } }我们可以把增、删、改封装到一个方法中:
public void update(String sql, Object ... args) { Connection connection = null; PreparedStatement ps = null; try { connection = JDBCUtils.getConnection(); ps = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) { System.out.println(args[i]); ps.setObject(i+1, args[i]); } ps.execute(); } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResource(connection, ps); } }测试:
@Test public void test3() { // String sql = "delete from customers where id = ?"; // update(sql, "12"); String sql2 = "update `order` set order_name = ? where order_id = ?"; update(sql2, "QQ", "4"); }这里要注意的是,order这个表的名字 “order” 是关键字,因此直接写order会报错,如果想让程序知道你想更新order是个表而不是关键字,就要向上述代码一样加个反引号
和增删改不同,查询是有结果返回的,因此获取连接、sql语句等步骤都是和增删改相同的,但要处理结果。
我们创建一个类来保存查到的数据(这里只写属性,方法略去了):
public class Customer { private int id; private String name; private String email; private Date birth; } @Test public void test1() { Connection connection = null; PreparedStatement ps = null; ResultSet resultSet = null; try { connection = JDBCUtils.getConnection(); String sql = "select id,name,email,birth from customers where id = ?"; ps = connection.prepareStatement(sql); ps.setObject(1, 20); resultSet = ps.executeQuery(); System.out.println(resultSet); if (resultSet.next()) { int id = resultSet.getInt("id"); String name = resultSet.getString(2); String email = resultSet.getString(3); Date birth = resultSet.getDate(4); Customer customer = new Customer(id, name, email, birth); System.out.println(customer); } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResource(connection, ps, resultSet); } }next的作用是看看后面还有没有元素,如果有就指针下移,但不返回结果。
注意,resultSet也需要关闭资源,因此在JDBCUtils中添加一个重载的方法:
public static void closeResource(Connection conn, PreparedStatement ps, ResultSet result) { try { if (conn!=null) conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } try { ps.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } try { result.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } }上面的代码只能查固定的属性,能不能写一个通用的方法,让我们想查几个属性就查几个属性呢?
// 查询的通用写法 public Customer query(String sql, Object... args) { Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; try { connection = JDBCUtils.getConnection(); ps = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } rs = ps.executeQuery(); // 获取元数据 ResultSetMetaData metaData = rs.getMetaData(); // 根据元数据获取查询结果的列数 int columnCount = metaData.getColumnCount(); if (rs.next()) { Customer c = new Customer(); // 写在这里最好,如果写在if外面,那么当rs为空的时候仍然会创建对象 for (int i = 0; i < columnCount; i++) { // 获取每列的值 Object columnValue = rs.getObject(i + 1); // 获取每列的列名 String columnName = metaData.getColumnName(i + 1); // 给Customer对象的columnName属性赋值为columnValue,利用反射 Field field = Customer.class.getDeclaredField(columnName); field.setAccessible(true); // 属性是私有的,不要忘了设置权限 field.set(c, columnValue); } return c; } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResource(connection, ps, rs); } return null; }说明:
由于我们想查可变数量的属性,所以sql中的属性个数是不定的我们还是先获取连接,把占位符设置好,然后执行查询如何知道查询结果中有几列呢?因此要获取结果集的列数(通过元数据(就是描述结果信息的数据),已在代码中标明)有了列数,就能通过循环来得到当前结果每一列的值,由于我们想把查询结果保存到对象中,因此还要知道当前列的属性名,同样通过元数据获得现在知道了列名和值,要给对象赋值了,如何赋值?通过反射!(已标注)调用:
@Test public void test2() { // String sql = "select id, name, birth, email from customers where id = ?"; String sql = "select id, name, email from customers where id = ?"; Customer customer = query(sql, "4"); System.out.println(customer); } 结果: Customer{id=4, name='汤唯', email='tangw@sina.com', birth=null}上面的代码是Java类中的属性名和数据库中的字段名完全相同的情况,但有时这二者并不会完全相同,比如下面这个表:
我们可能在对应的类里起的名字是:
public class Order { private int orderId; private String orderName; private Date orderDate; }如果还像前面那样查询,就会报错:
// Java类的属性名和数据库中属性名不相同,例如Order类 public Order orderQuery(String sql, Object... args) { Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; try { connection = JDBCUtils.getConnection(); ps = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) { ps.setObject(i+1, args[0]); } rs = ps.executeQuery(); ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); if (rs.next()) { Order order = new Order(); for (int i = 0; i < columnCount; i++) { Object columnValue = rs.getObject(i+1); String columnName = metaData.getColumnName(i + 1); Field field = Order.class.getDeclaredField(columnName); field.setAccessible(true); field.set(order, columnValue); } return order; } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResource(connection, ps, rs); } return null; } @Test public void orderTest() { String sql = "select order_id, order_name, order_date from `order` where order_id = ?"; Order order = orderQuery(sql, 2); System.out.println(order); }结果:
这是因为Java类中的属性和数据库的字段名不匹配。
解决方案是起别名,在sql查询的时候可以起别名,让这个别名和Java类中的属性相同即可:
@Test public void orderTest() { String sql = "select order_id orderId, order_name orderName, order_date orderDate from `order` where order_id = ?"; Order order = orderQuery(sql, 2); System.out.println(order); }但是此时运行还是报错,因为metaData.getColumnName得到的是列名而不是别名,要使用getColumnLabel:
这个getColumnLabel是你起了别名他就返回别名,没起名字就返回列名。
上述代码可以针对一个表的若干属性进行查询,现在想对不同表的多个属性来进行查询。
// 多表不同属性的查询 public <T> T queryMulti(Class<T> clazz, String sql, Object ... args) { Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; try { connection = JDBCUtils.getConnection(); ps = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) { ps.setObject(i+1, args[i]); } rs = ps.executeQuery(); ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); if (rs.next()) { T t = clazz.newInstance(); for (int i = 0; i < columnCount; i++) { Object columnValue = rs.getObject(i + 1); String columnLable = metaData.getColumnLabel(i + 1); Field field = clazz.getDeclaredField(columnLable); field.setAccessible(true); field.set(t, columnValue); } return t; } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResource(connection, ps, rs); } return null; } @Test public void multiTable() { String sql = "select name, birth, email from customers where id = ?"; Customer customer = queryMulti(Customer.class, sql, 3); System.out.println(customer); String sql2 = "select order_id orderId, order_name orderName from `order` where order_id = ?"; Order order = queryMulti(Order.class, sql2, 4); System.out.println(order); } 结果: Customer{id=0, name='林志玲', email='linzl@gmail.com', birth=1984-06-12} Order{orderId=4, orderName='QQ', orderDate=null}说明:
这里的改动主要就是使用了泛型,在调用查询方法的时候指定要查询的表名注意泛型方法的声明:public <T> T queryMulti(Class<T> clazz, String sql, Object … args),不能直接写成public T queryMulti(Class<T> clazz, String sql, Object … args),因为这么写编译器会认为这个T是一个类而不是泛型。现在我们想让查询结果中包含多条记录。
只需用集合即可:
// 查询结果包含多条记录 public <T> List<T> queryMultiItems(Class<T> clazz, String sql, Object ... args) { Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; try { connection = JDBCUtils.getConnection(); ps = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) { ps.setObject(i+1, args[i]); } rs = ps.executeQuery(); ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); ArrayList<T> list = new ArrayList<>(); while (rs.next()) { T t = clazz.newInstance(); for (int i = 0; i < columnCount; i++) { Object columnValue = rs.getObject(i + 1); String columnLable = metaData.getColumnLabel(i + 1); Field field = clazz.getDeclaredField(columnLable); field.setAccessible(true); field.set(t, columnValue); } list.add(t); } return list; } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResource(connection, ps, rs); } return null; } @Test public void multiItems() { String sql = "select email, id, name from customers where id < ?"; List<Customer> customerList = queryMultiItems(Customer.class, sql, 10); customerList.forEach(s -> System.out.println(s)); } Customer{id=1, name='汪峰', email='wf@126.com', birth=null} Customer{id=2, name='王菲', email='wangf@163.com', birth=null} Customer{id=3, name='林志玲', email='linzl@gmail.com', birth=null} Customer{id=4, name='汤唯', email='tangw@sina.com', birth=null} Customer{id=5, name='成龙', email='Jackey@gmai.com', birth=null} Customer{id=6, name='迪丽热巴', email='reba@163.com', birth=null} Customer{id=7, name='刘亦菲', email='liuyifei@qq.com', birth=null} Customer{id=8, name='陈道明', email='bdf@126.com', birth=null}https://www.bilibili.com/video/BV1eJ411c7rf?p=23
注意,之前都是用execute直接执行的,但他是有返回值的,如果是查询操作就返回true,如果是更新操作就返回false。而executeUpdate()方法是针对更新操作说的,返回0代表没有item受到影响,返回大于0的数代表受影响的行数
第一问和上一题很相似了,这里做一下第二问和第三问(只实现了按准考证查找,身份证类似):
package practice; import bean.java.Student; import utils.JDBCUtils; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.util.Scanner; public class task2 { public static void main(String[] args) { System.out.println("请选择查询方式:"); System.out.println("a 准考证号"); System.out.println("b 身份证号"); Scanner scanner = new Scanner(System.in); String s = scanner.next(); if ("a".equalsIgnoreCase(s)) { System.out.println("请输入准考证号"); String examCard = scanner.next(); String sql = "select FlowID flowID, Type type, IDCard idCard, ExamCard examCard, StudentName name, " + "Location location, Grade grade from examstudent where examCard=?"; Student student = new task2().queryMulti(Student.class, sql, examCard); System.out.println(student); }else if ("b".equalsIgnoreCase(s)) { } } public <T> T queryMulti(Class<T> clazz, String sql, Object ... args) { Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; try { connection = JDBCUtils.getConnection(); ps = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) { ps.setObject(i+1, args[i]); } rs = ps.executeQuery(); ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); if (rs.next()) { T t = clazz.newInstance(); for (int i = 0; i < columnCount; i++) { Object columnValue = rs.getObject(i + 1); String columnLable = metaData.getColumnLabel(i + 1); Field field = clazz.getDeclaredField(columnLable); field.setAccessible(true); field.set(t, columnValue); } return t; } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResource(connection, ps, rs); } return null; } }第三问:
public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("input the examId: "); String s = scanner.next(); String sql = "select FlowID flowID, Type type, IDCard idCard, ExamCard examCard, StudentName name, " + "Location location, Grade grade from examstudent where examCard=?"; Student student = new task2().queryMulti(Student.class, sql, s); if (student!=null) { String sql1 = "delete from examstudent where examCard = ?"; int i = new task2().update(sql1, s); if (i > 0) { System.out.println("success!"); } } }这里没有用try,只是表达个意思:
@Test public void test() throws Exception { Connection connection = JDBCUtils.getConnection(); String sql = "insert into insert_test(name) values(?)"; PreparedStatement ps = connection.prepareStatement(sql); long l1 = System.currentTimeMillis(); for (int i = 0; i < 20000; i++) { ps.setObject(1, "name" + i); ps.execute(); } long l2 = System.currentTimeMillis(); System.out.println(l2-l1); JDBCUtils.closeResource(connection, ps); } 结果: 26479这种方式每填充完一次占位符就执行一次,这样和磁盘交互次数较多导致效率不高,因此可以用如下方式:
但使用Batch之前还要做几处配置:
https://www.bilibili.com/video/BV1eJ411c7rf?p=34
他们都是带着sql语句到数据库中执行操作的。
后者存在弊端:拼串和sql注入。
前者还能实现对Blob字段的操作。
前者批量插入数据时更高效:预编译
前者的诸多优点都是源于预编译: