一.获取数据库连接
1.方式一
public class ConnectionTest { @Test public void testConnection1() throws SQLException { //获取Driver实现类对象 Driver driver = new com.mysql.jdbc.Driver(); //jdbc:mysql: 协议 //localhost ip地址 //3306 MySQL数据库默认端口号 //test 需要连接的数据库名称 String url = "jdbc:mysql://localhost:3306/test"; Properties info = new Properties(); //设置连接的用户名和名称,user和password是固定的写法 info.setProperty("user","root"); info.setProperty("password","lxq"); Connection connect = driver.connect(url, info); System.out.println(connect); } }
2.方式二
public class ConnectionTest { @Test public void testConnection2() throws Exception { //1.使用反射获取Driver实现类对象 Class<?> clazz = Class.forName("com.mysql.jdbc.Driver"); Driver driver = (Driver) clazz.newInstance(); //2.提供需要连接的数据库 String url = "jdbc:mysql://localhost:3306/test"; //3.提供连接需要的用户和密码 Properties info = new Properties(); info.setProperty("user","root"); info.setProperty("password","lxq"); //获取链接 Connection connect = driver.connect(url, info); System.out.println(connect); } }
- 方式二是方式一的迭代,与方式一相比没有出现第三方的API,有较好移植性
3.方式三
public class ConnectionTest { @Test public void testConnection3() throws Exception { //1.使用反射获取Driver实现类对象 Class<?> clazz = Class.forName("com.mysql.jdbc.Driver"); Driver driver = (Driver) clazz.newInstance(); //2.提供需要连接的数据库 String url = "jdbc:mysql://localhost:3306/test"; //3.提供连接需要的用户和密码 String user = "root"; String password = "lxq"; //4.注册驱动 DriverManager.registerDriver(driver); //5.获取连接 Connection connection = DriverManager.getConnection(url, user, password); System.out.println(connection); } }
- 使用DriverManager(驱动管理器)替换Driver,DriverManager是jdk提供的一个类,用来完成获取连接的操作
4.方式四
public class ConnectionTest { @Test public void testConnection4() throws Exception { //1.提供连接所需信息 String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "lxq"; //2.使用反射加载驱动 Class.forName("com.mysql.jdbc.Driver"); //3.获取连接 Connection connection = DriverManager.getConnection(url, user, password); System.out.println(connection); } }
- 与方式三相比,方式四省略了如下代码
Driver driver = (Driver) clazz.newInstance(); DriverManager.registerDriver(driver);
- 理由是,在com.mysql.jdbc.Driver类中有如下所示的静态代码块,使用Class.forName()加载这个驱动时就会执行这个静态代码块,实现了注册驱动
static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } }
5.方式五
public class ConnectionTest { @Test public void testConnection5() throws Exception { //1.获取连接所需信息 InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties"); Properties info = new Properties(); info.load(is); String url = info.getProperty("url"); String user = info.getProperty("user"); String password = info.getProperty("password"); String driverClass = info.getProperty("driverClass"); //2.使用反射加载驱动 Class.forName(driverClass); //5.获取连接 Connection connection = DriverManager.getConnection(url, user, password); System.out.println(connection); } }
- 其中配置文件jdbc.properties在src目录下
url=jdbc:mysql://localhost:3306/test user=root password=lxq; driverClass=com.mysql.jdbc.Driver
- 使用配置文件的好处
- 实现代码和数据的分离,如果需要修改配置信息,直接在配置文件中进行修改,不需要深入代码
- 如果修改了配置信息,省去了重新编译的过程
二.使用PreparedStatement对数据库进行CRUD
1.数据库连接和关闭的工具类
public class JDBCUtils { public static Connection getConnection() throws Exception{ InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties info = new Properties(); info.load(is); String url = info.getProperty("url"); String user = info.getProperty("user"); String password = info.getProperty("password"); String driverClass = info.getProperty("driverClass"); Class.forName(driverClass); Connection connection = DriverManager.getConnection(url, user, password); return connection; } public static void closeConnection(Connection conn, Statement ps){ try { if (ps != null) { ps.close(); } } catch (SQLException e) { e.printStackTrace(); } try { if (conn != null) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }
2.Statement介绍及其弊端
- 通过调用Connection对象的createStatement()方法创建该对象。该对象用于执行静态的SQL语句,并且返回执行结果
- Statement接口中定义了下列方法用于执行SQL语句
int excuteUpdate(String sql) //执行更新操作INSERT、UPDATE、DELETE
ResultSet executeQuery(String sql) //执行查询操作SELECT
- 使用Statement操作数据表存在弊端
- 问题一:存在拼串操作,繁琐
- 问题二:存在SQL注入问题
- SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令(如:SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1') ,从而利用系统的 SQL 引擎完成恶意行为的做法
- 对于Java而言,要防范SQL注入,只要用PreparedStatement(从Statement扩展而来)取代Statement就可 以了
3.PreparedStatement介绍
- PreparedStatement接口是Statement的子接口,它表示一条预编译过的 SQL 语句
- 可以通过调用Connection对象的preparedStatement(String sql)方法获取PreparedStatement对象
- PreparedStatement对象所代表的 SQL 语句中的参数用问号(?)来表示,调用PreparedStatement对象的setXxx()方法来设置这些参数,setXxx()方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值
4.PreparedStatement与Statement的比较
- 代码的可读性和可维护性高
- PreparedStatement能最大可能提高性能
- DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行
- 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义,事实是没有数据库会对普通语句编译后的执行代码缓存,这样每执行一次都要对传入的语句编译一次
- PreparedStatement可以防止SQL注入
5.使用PreparedStatement实现插入数据的操作
public class PreparedStatementUpdateTest { @Test public void testInsert() { Connection connection = null; PreparedStatement ps = null; try { //1.获取链接 connection = JDBCUtils.getConnection(); //2.预编译sql语句,返回PreparedStatement实例 String sql = "insert into customers(name,email,birth) value(?,?,?)"; ps = connection.prepareStatement(sql); //3.填充占位符 ps.setString(1,"哪吒"); ps.setString(2,"nezha@gmail.com"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); java.util.Date parse = sdf.parse("2001-03-21"); long time = parse.getTime(); //这个Date是sql中的Date ps.setDate(3,new Date(time)); //4.执行操作 ps.execute(); } catch (Exception e) { e.printStackTrace(); } //5.资源释放 JDBCUtils.closeConnection(connection,ps); } }
6.通用的增删改操作方式
public class PreparedStatementUpdateTest { public void update(String sql,Object ...obj){ Connection conn = null; PreparedStatement ps = null; try { //1.获取链接 conn = JDBCUtils.getConnection(); //2.预编译sql语句,返回PreparedStatement实例 ps = conn.prepareStatement(sql); //3.填充占位符 for(int i=0;i<obj.length;i++){ ps.setObject(i+1,obj[i]); } //4.执行操作 ps.execute();//如果需要得到更新后影响的行数,可以改为executeUpdate方法,然后修改返回值类型和增加异常时的输出即可 } catch (Exception e) { e.printStackTrace(); }finally { //5.释放资源 JDBCUtils.closeConnection(conn,ps); } } }
7.使用PrepareStatement实现查询数据的操作
- 查询的代码
public class CustomerForQuery { public Customer commonQueryForCustomer(String sql,Object ...obj){ Connection conn = null; PreparedStatement ps = null; ResultSet resultSet = null; try { //1.获取连接 conn = JDBCUtils.getConnection(); //2.预编译sql语句,返回PreparedStatement实例 ps = conn.prepareStatement(sql); //3.填充占位符 for (int i = 0; i < obj.length; i++) { ps.setObject(i+1,obj[i]); } //4.执行查询操作 resultSet = ps.executeQuery(); //5.获取描述结果集的对象 ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); //6.处理结果集 if(resultSet.next()){ Customer customer = new Customer(); for (int i = 0; i < columnCount; i++) { Object object = resultSet.getObject(i + 1); String columnName = metaData.getColumnName(i + 1); Field declaredField = Customer.class.getDeclaredField(columnName); declaredField.setAccessible(true); declaredField.set(customer,object); } return customer; } } catch (Exception e) { e.printStackTrace(); }finally { //7.关闭资源 JDBCUtils.closeConnection(conn,ps,resultSet); } return null; } @Test public void test(){ String sql = "select name,email from customers where id=?"; Customer customer = commonQueryForCustomer(sql, 2); System.out.println(customer); } }
- 用到的bean类
public class Customer { private int id; private String name; private String email; private Date birth; public Customer() {} public Customer(int id, String name, String email, Date birth) { this.id = id; this.name = name; this.email = email; this.birth = birth; } 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; } @Override public String toString() { return "Customer{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + ", birth=" + birth + '}'; } }
8.通用的查询操作方式
- 查询结果是一条记录的情况
public class CommonQuery { public <T> T getInstance(Class<T> clazz,String sql,Object...obj){ Connection conn = null; PreparedStatement ps = null; ResultSet resultSet = null; try { //1.获取数据库连接 conn = JDBCUtils.getConnection(); //2.预编译sql语句,返回PreparedStatement实例 ps = conn.prepareStatement(sql); //3.填充占位符 for (int i = 0; i < obj.length; i++) { ps.setObject(i+1,obj[i]); } //4.执行查询操作 resultSet = ps.executeQuery(); //5.获取结果集描述对象,从而获得结果集列数 ResultSetMetaData metaData = ps.getMetaData(); int columnCount = metaData.getColumnCount(); //6.处理结果集 if (resultSet.next()){ T t = clazz.newInstance(); for (int i = 0; i < columnCount; i++) { Object object = resultSet.getObject(i + 1); String columnName = metaData.getColumnName(i + 1);//获取当前列名 Field declaredField = clazz.getDeclaredField(columnName);//由列名反射获取对应属性 declaredField.setAccessible(true); declaredField.set(t,object); } return t; } } catch (Exception e) { e.printStackTrace(); }finally { //7.关闭资源 JDBCUtils.closeConnection(conn,ps,resultSet); } return null; } //测试 @Test public void test(){ String sql = "select name,email,birth from customers where id=?"; Customer instance = getInstance(Customer.class, sql, 5); System.out.println(instance); } }
- 查询结果是多条记录的情况
public class CommonQuery { public <T> List<T> getInstances(Class<T> clazz,String sql,Object...obj){ Connection conn = null; PreparedStatement ps = null; ResultSet resultSet = null; try { //1.获取数据库连接 conn = JDBCUtils.getConnection(); //2.预编译sql语句,返回PreparedStatement实例 ps = conn.prepareStatement(sql); //3.填充占位符 for (int i = 0; i < obj.length; i++) { ps.setObject(i+1,obj[i]); } //4.执行查询操作 resultSet = ps.executeQuery(); //5.获取结果集描述对象,从而获得结果集列数 ResultSetMetaData metaData = ps.getMetaData(); int columnCount = metaData.getColumnCount(); //6.创建用于返回的集合 ArrayList<T> list = new ArrayList<>(); //7.处理结果集 while (resultSet.next()){ T t = clazz.newInstance(); for (int i = 0; i < columnCount; i++) { Object object = resultSet.getObject(i + 1); String columnName = metaData.getColumnLabel(i + 1);//注意此处getColumnLabel与getColumnName的区别 Field declaredField = clazz.getDeclaredField(columnName); declaredField.setAccessible(true); declaredField.set(t,object); } list.add(t); } return list; } catch (Exception e) { e.printStackTrace(); }finally { //8.关闭资源 JDBCUtils.closeConnection(conn,ps,resultSet); } return null; } @Test public void test(){ String sql="select name,email,birth from customers where id>?"; List<Customer> instances = getInstances(Customer.class, sql, 10); System.out.println(instances); String sql1 = "select order_id id,order_name name,order_date date from `order` where order_id>?"; List<Order> instances1 = getInstances(Order.class, sql1,0); System.out.println(instances1); } }
9.ResultSet和ResultSetMetaData
- ResultSet
- 查询需要调用PreparedStatement 的 executeQuery() 方法,查询结果是一个ResultSet 对象
- ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商提供实现
- ResultSet 返回的实际上就是一张数据表。有一个指针指向数据表的第一条记录的前面
- ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象 的 next() 方法移动到下一行。调用 next()方法检测下一行是否有效。若有效,该方法返回 true,且指针下移。 相当于Iterator对象的 hasNext() 和 next() 方法的结合体。 当指针指向一行时, 可以通过调用 getXxx(int index) 或 getXxx(int columnName) 获取每一列的值。 例如: getInt(1), getString("name")
- 注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始
- ResultSetMetaData
- 可用于获取关于ResultSet对象中列的类型和属性信息的对象
- 通过ResultSet的getMetaData()方法获取
- 常用方法
- getColumnName(int column)方法,获取指定列的名称
- getColumnLabel(int column)方法,获取指定列的别名,如果不指定别名则返回名称
- getColumnCount()方法,返回当前 ResultSet 对象中的列数
10.资源释放
- 释放ResultSet, Statement,Connection。
- 数据库连接(Connection)是非常稀有的资源,用完后必须马上释放,如果Connection不能及时正确的关闭将导致系统宕机
- Connection的使用原则是尽量晚创建,尽量早的释放。 可以在finally中关闭,保证及时其他代码出现异常,资源也一定能被关闭。
11.小结
- 两种思想
- 面向接口编程
- ORM(Object Relational Mapping)
- 一个数据表对应一个java类
- 表中一条记录对应java类一个对象
- 表中一个字段对应java类一个属性
- 两种技术
- JDBC结构集的元数据:ResultSetMetaData
- 获取列数
- 获取列别名
- 通过反射,创建指定类对象,获取指定属性并赋值
三.操作BLOB类型字段
1.MYSQL中的BLOB类型字段
- MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据
- 插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的
- MySQL的四种BLOB类型
- TinyBlob,最大存放255B
- Blob,最大存放65KB
- MediumBlob,最大存放16MB
- LongBlob,最大存放4GB
- 实际使用中根据需要存入的数据大小定义不同的BLOB类型
- 需要注意的是:如果存储的文件过大,数据库的性能会下降
- 如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如 下的配置参数: max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务
2.插入Blob类型数据
//获取连接 Connection conn = JDBCUtils.getConnection(); String sql = "insert into customers(name,email,birth,photo)values(?,?,?,?)"; PreparedStatement ps = conn.prepareStatement(sql); //填充占位符 ps.setString(1, "徐海强"); ps.setString(2, "xhq@126.com"); ps.setDate(3, new Date(new java.util.Date().getTime())); //操作Blob类型的变量 FileInputStream fis = new FileInputStream("xhq.png"); ps.setBlob(4, fis); //执行 ps.execute(); fis.close(); JDBCUtils.closeResource(conn, ps)
3.查询Blob类型数据
String sql = "SELECT id, name, email, birth, photo FROM customer WHERE id = ?"; conn = getConnection(); ps = conn.prepareStatement(sql); ps.setInt(1, 8); rs = ps.executeQuery(); if(rs.next()){ Integer id = rs.getInt(1); String name = rs.getString(2); String email = rs.getString(3); Date birth = rs.getDate(4); Customer cust = new Customer(id, name, email, birth); System.out.println(cust); //读取Blob类型的字段 Blob photo = rs.getBlob(5); InputStream is = photo.getBinaryStream();//从查询出来的Blob对象中获取二进制流 OutputStream os = new FileOutputStream("c.jpg"); byte [] buffer = new byte[1024]; int len = 0; while((len = is.read(buffer)) != -1){ os.write(buffer, 0, len); } JDBCUtils.closeResource(conn, ps, rs); if(is != null){ is.close(); } if(os != null){ os.close(); }