- JDBC基础
- 持久化:把数据存到可掉电式存储设备中以供以后使用
- JDBC:Java Database Connectivity,独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口
可以将其理解为SUN公司提供的一套API,可以实现对具体数据库的操作(获取连接、关闭连接、DML、DDL、DCL) - 使用JDBC带来的好处
- 面向应用的API:Java API,供开发人员使用的抽象接口,不需关注具体的数据库细节
- 面向数据库的API:Java Driver API,供开发商开发数据库驱动的接口,只需提供标准接口的实现即可
- 将JDBC所需jar包引入maven工程
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.18</version> </dependency>
- 基本的JDBC操作过程
- 获取连接的几种方法(Connection的使用)
- 通过Driver类获取
//1. 创建一个Driver实现类的对象 Driver driver = new com.mysql.cj.jdbc.Driver(); //2. 准备连接数据库的基本信息:url、user、password String url = "jdbc:mysql://localhost:3306/mysql?serverTimezone=UTC"; Properties info = new Properties(); info.put("user", "xxxx"); info.put("password", "xxxxxxxx"); //3. 调用Driver接口的connect(url,info)获取数据库连接 Connection connection = driver.connect(url, info);
- 读取配置文件,通过DriverManager创建数据库连接
public static Connection getConnection() { //1. 初始化字符串存储driver、url、user、password等必要信息 String driver = null; String url = null; String user = null; String passwd = null; //2. 创建Properties对象,将一个文件通过输入流绑定到该对象 Properties properties = null; try { properties = new Properties(); InputStream is = JDBCTools.class.getClassLoader().getResourceAsStream("jdbc.properties"); properties.load(is); } catch (IOException e) { e.printStackTrace(); } //3. 通过绑定后的Properties对象,获取user等信息的真实值 driver = properties.getProperty("driver"); url = properties.getProperty("jdbcUrl"); user = properties.getProperty("user"); passwd = properties.getProperty("password"); //4. 注册数据库驱动 Connection connection = null; try { Class.forName(driver); //5. 并通过DriverManager获取数据库连接 connection = DriverManager.getConnection(url, user, passwd); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return connection; }
- 简单的更新操作(Statement的使用)
//1.获取数据库连接 //2. 准备数据插入sql语句 String sql = "INSERT INTO Data" + "(id, name, email, birth)" + "VALUES" + "(1,'hkk', '@cdc.com', '1988-08-10')"; //3. 执行插入操作:Statement + executeUpdate Statement statement = null; try { statement = connection.createStatement(); statement.executeUpdate(sql); } catch (SQLException e) { e.printStackTrace(); }
- 简单的查询操作 (ResultSet的使用)
//1. 获取数据库连接 //2. 获取Statement对象 Statement stat = conn.createStatement(); String sql = "SELECT * FROM data"; ResultSet rs = null; try { //3. 执行查询 rs = stat.executeQuery(sql); //4. 处理ResultSet while (rs.next()) { int id = rs.getInt("id"); String name = rs.getString("name"); String email = rs.getString("email"); String birth = rs.getString("birth"); System.out.println("id=" + id + " name=" + name + " email=" + email + " birth=" + birth); } } catch (SQLException e) { e.printStackTrace(); }
- 释放对数据库连接的占用
public static void closeConnection(Connection conn, Statement stat, ResultSet rs) { try { if (rs != null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stat != null) stat.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } }
- 获取连接的几种方法(Connection的使用)
- PreparedSatement的使用
- Statement存在的问题:
- 拼接sql语句十分繁琐
- 存在sql注入问题
- Statement无法操作BLOB类型变量
- Statement批量插入时候效率较低
- PreparedStatement有关概念
- 其本身是Statement的一个子接口
- 其本质是一个预编译的SQL语句
- 使用PreparedStatement实现基本的增删改查操作
- 使用Statement语句,需要拼接sql语句,繁琐易错
- 本质上是Statement的子接口,传入的sql语句中,带有占位符,顺带控制了sql注入
- 构造初始sql语句,String sql = “INSERT INTO xxx VALUES(???)”
- 通过connection获取preparedStatement对象
- 调用PreparedStatement的setXxxx(int index, Object val)
- 执行数据库操作
- 关闭数据库连接
public void test1(){ //1. 初始化sql语句 String sql = "INSERT INTO student(id, name, school) VALUES (?,?,?)"; //2. 通过connection获取PreparedStatement对象 Connection connection = null; PreparedStatement ps = null; try { connection = JDBCTools.getConnection(); ps = connection.prepareStatement(sql); } catch (SQLException e) { e.printStackTrace(); } //3. 通过PreparedStatement对象执行数据库操作 try { ps.setObject(1, "0000000000"); ps.setObject(2, "fmr"); ps.setObject(3,"CQUPT"); ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } //3. 关闭有关连接 try { if(ps != null) ps.close(); if(connection != null) connection.close(); } catch (SQLException e) { e.printStackTrace(); } }
- 使用PreparedStatement操作Blob对象
- 若无法写入Blob对象,需修改MySQL配置文件my.ini,添加参数max_allowed_packet = 16M
- 代码示例
/* 向数据库插入BLOB数据 1. 建立数据库连接、PreparedStatement对象 2. 填充sql占位符,在这个过程中插入BLOB数据 3. 执行update操作 */ @Test public void testBlobToDB() { Connection conn = null; PreparedStatement ps = null; String sql = "INSERT INTO pictures(owner, pict) VALUES(?,?)"; try { conn = JDBCTools.getConnection(); ps = conn.prepareStatement(sql); ps.setObject(1, "fmr"); File f = new File("C:\\Users\\fmr\\Pictures\\memories\\xxx.png"); InputStream in = new FileInputStream(f); ps.setBlob(2, in, (int)f.length()); } catch (SQLException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } try { ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } try { if (ps != null) ps.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } }
/* 从数据库获取BLOB数据 1.在ResultSet中,使用getBlob()方法获取BLOB对象 2. 调用BLOB的getBinaryStream()方法得到输入流 3. 构建输出流,将读取的BLOB写入特定的文件 */ @Test public void testBlobFromDB() { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; String sql = "SELECT owner, pict FROM pictures"; FileOutputStream fos = null; InputStream in = null; try { conn = JDBCTools.getConnection(); ps = conn.prepareStatement(sql); } catch (SQLException e) { e.printStackTrace(); } try { rs = ps.executeQuery(); } catch (SQLException e) { e.printStackTrace(); } try { if (rs.next()) { //1.在ResultSet中,使用getBlob()方法获取BLOB对象 Blob p = rs.getBlob(2); //2. 调用BLOB的getBinaryStream()方法得到输入流 in = p.getBinaryStream(); //3. 构建输出流,将读取的BLOB写入特定的文件 fos = new FileOutputStream("C:\\Users\\fmr\\Desktop\\xxx.png"); byte[] buffer = new byte[1024]; int length; while ((length = in.read(buffer)) != -1) { fos.write(buffer, 0, length); } } } catch (SQLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { if (fos != null) fos.close(); if(in != null) in.close(); if(rs!= null) rs.close(); if(ps != null) ps.close(); if(conn != null) conn.close(); } catch (IOException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } }
- Statement存在的问题:
- DAO的使用
- DAO指的是访问数据信息的类,包含了对数据的CRUD,而不包含任何业务逻辑
- JavaBean:
- 在JavaEE中,Java类的属性是通过getter、setter方法定义的。去掉get、set后,
将首字母小写,即获得属性名称 - JavaSE中说的属性,即成员变量,称之为字段
- 一般字段名和属性名是一致的
- 操作Java类的属性有一个工具包:beanutils
- 使用BeanUtil工具,可以替换DAO类的query方法中,用反射为类属性赋值的操作
- 在JavaEE中,Java类的属性是通过getter、setter方法定义的。去掉get、set后,
- 元数据:
- 描述数据的数据,可从中获取结果集中有多少列,列名是什么等与数据表本身有关的信息
- 调用ResultSet的getMetaData()方法,获得ResultSetMetaData对象
- int getColumnCount():sql语句中包含哪些列
- String getColumnLabel(int column):获取指定的列的别名,索引从1开始
- 两种数据库操作编程思想
- 面向接口编程:开发者不关心具体实现,只需调用格式统一的接口即可
- ORM编程思想(Object Relational Mapping):
- 一个数据表对应一个Java类
- 数据表中的一条记录对应Java类的一个对象
- 数据表中的一个字段对应Java类的一个属性
- 两种重要技术的使用
- ResultSet的使用,注意:如何sql语句中没有指定列别名,那么getColumnLabel()返回的就是列名
- 反射技术的使用,为返回的类对象设置属性值
- 创建对应的运行时类对象
- 在运行时动态地调用指定的运行时类的属性或者方法
- 使用JDBC编写DAO可能包含的方法:
- void update(String sql, Object ... args);//对应INSERT、UPDATE、DELETE操作
- <T> T get(String sql, Object .. args, Class<T> clazz)//查询一条记录
- <T> List<T> getForList(String sql, Object ... args, Class clazz)//查询多条记录,以集合形式返回
- <E> E getForValue(String sql, Object ... args)//返回某条记录某个字段的值或者一个统计值
- 自定义DAO的查询方法的实现 (将泛型与PreparedStatement结合使用)
- 获取Connection
- 获取PreparedStatement
- 填充占位符
- 进行查询,得到ResultSet
- 通过ResultSet获取ResultSetMetaData
- 准备一个Map<String, Object>,key:列别名,value:列值
- 通过ResultSetMetaData得到结果集中有多少列
- 通过ResultSetMetaData得到结果集中各列的列别名
- 通过ResultSet得到列别名对应的列值
- 用反射创建Class对应的对象
- 遍历Map,使用反射将Map中的value,赋给类属性
- 返回类对象
- DAO中查询方法示例
//查询一条记录,返回对应的对象 public <T> T get(Class<T> clazz, String sql, Object ... args ){ T entity = null; Connection conn = null; PreparedStatement ps = null; try { //1. 获取COnnection conn = JDBCTools.getConnection(); //2. 获取PreparedStatement ps = conn.prepareStatement(sql); } catch (SQLException e) { e.printStackTrace(); } //3. 填充SQL语句中的占位符 try { for (int i = 0; i < args.length; i++) { ps.setObject(i+1, args[i]); } } catch (SQLException e) { e.printStackTrace(); } //4. 进行查询操作,得到ResultSet ResultSet rs = null; ResultSetMetaData rsmd = null; try { rs = ps.executeQuery(); } catch (SQLException e) { e.printStackTrace(); } //5. 准备Map<String, Object> 列别名:列的值 Map<String, Object> tmp = new HashMap<String, Object>(); //6. 获取ResultSetMetaData try { if(rs.next()){ rsmd = rs.getMetaData(); //7. 根据ResultSetMetaData对象,获取列数,这个列数是从1开始的 for (int i = 0; i < rsmd.getColumnCount(); i++) { //8. 逐个获取列别名、列的值,并写入Map String columnLabel = rsmd.getColumnLabel(i+1); Object columnValue = rs.getObject(columnLabel); tmp.put(columnLabel, columnValue); } } } catch (SQLException e) { e.printStackTrace(); } //9. 根据Map中的列别名、列的值,调用反射为返回的类对象赋值,注意,该类必须有默认无参构造器 for (Map.Entry<String,Object> entry:tmp.entrySet()){ ReflectionUtils.setFieldValue(entity, entry.getKey(), entry.getValue()); } //10. 关闭相关连接,并返回对象 try { if(ps!= null) ps.close(); if(conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } return entity; } static void setFieldValue(Object obj, String fieldName, Object fieldValue ){ try { Class clazz = obj.getClass(); Field f = clazz.getDeclaredField(fieldName); f.setAccessible(true); f.set(obj, fieldValue); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }
- 数据库批量操作的实现
- 层次一:Statement实现
- 层次二:PreparedStatement替换Statement实现
- 层次三:使用addBatch()/executeBatch()/clearBatch()
- 层次四:使用batch的同时关闭自动提交
- batch的代码示例
public static void main(String[] args) { Connection conn = null; String sql = "INSERT INTO testjob(id, name, banlance)VALUES (?,?,?)"; PreparedStatement ps = null; try { conn = JDBCTools.getConnection(); ps = conn.prepareStatement(sql); JDBCTools.beginTransition(conn); for (int i = 1; i <= 1000; i++) { ps.setObject(1, i + 2); ps.setObject(2, "fmr"); ps.setObject(3, (double) i + 2); ps.addBatch(); if(i % 10 == 0){ ps.executeUpdate(); ps.clearBatch(); } } //conn.commit(); JDBCTools.commit(conn); } catch (SQLException e) { e.printStackTrace(); try { if(conn != null) { conn.rollback(); } } catch (SQLException e1) { e1.printStackTrace(); } } JDBCTools.releaseConnection(null,ps,null,conn); } public static void beginTransition(Connection conn){ try { conn.setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } } public static void releaseConnection(ResultSet rs,PreparedStatement ps, Statement stat, Connection conn) { try { if (rs != null) rs.close(); if(ps != null) ps.close(); if (stat != null) stat.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } }
- 数据库事务
- 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态
- 为确保数据库中数据的一致性,数据操纵应该是离散的成组的逻辑单元,
全部逻辑单元完成才算该事务成功,有任何一个逻辑单元失败就算该事务全部失败
所有从起始点以后的操作应该全部回退到开始状态 - 提交(COMMIT):开始一个事务,对数据进行操作,提交后,这些操作就永久保存下来
- 回退(ROLLBACK):DBA放弃所作的全部修改而回到事务开始时的状态
- 事务的ACID属性
- 原子性(Atomic):事务不可分割,一个事务中的操作要么都发生要么都不发生
- 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另一个一致性状态
- 隔离性(Isolation):并发的各个事务之间不能相互干扰,
一个事务内部的操作及所用的数据对并发的其他事务是隔离的 - 持久性(Durability):一个事务一旦被提交,它对数据库中数据的改变就是永久性的,
接下来的其他操作和数据库故障对其没有任何影响
- 数据库读写中可能出现的问题
- 脏读:对于两个事务T1、T2,T1读取了已经被T2更新但还没有被提交的字段之后,
若T2回滚,则T1读取的内容就是临时且无效的 - 不可重复读:对于两个事务T1、T2,T1读取了一个字段,然后T2更新了该字段,
之后,T1再次读取同一个字段,值就不同了 - 幻读:对于两个事务T1、T2,T1从一个表中读取了一个字段,
然后T2在该表中插入了一些新的行,之后,若T1再次读取同一个表,就会多出几行
- 脏读:对于两个事务T1、T2,T1读取了已经被T2更新但还没有被提交的字段之后,
- 数据库提供的4种事务隔离级别
- READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更。
脏读、不可重复读、幻读的问题都会出现 - READ COMMITTED(读已提交数据):只允许事务读取已被其他事务提交的变更
可以避免脏读,但不可重复读、幻读的问题依然存在 - REPEATABLE READ(可重复读):确保事务可以多次从一个字段种读取相同的值,在该事务持续期间,
禁止其他事务对该字段进行更新。可以避免脏读、不可重复读,但幻读问题依然存在 - SERIALIZABLE(串行化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,
禁止其他事务对该表执行插入、删除、更新操作。所有并发问题都可以解决,但性能很差 - 数据库系统必须具有隔离并发运行各个事务的能力,使他们之间互不影响
- 一个事务与其他事务的隔离的程度称为隔离级别。隔离性越高,数据一致性越好,并发性能越差
- READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更。
- 在数据库中设置隔离级别
- Oracle数据库支持2种隔离级别:READ COMMITED \ SERIALIZABLE,前者为默认值
- MySQL数据库支持4种隔离级别,默认为REPEATABLE READ
- 将事务应用到数据库中的操作
- 获取数据库连接(手动获取连接、通过数据库连接池获取连接)
- 将一个或多个DML操作,作为一个事务出现
- 手动使用PreparedStatement实现sql操作
- 使用dbUtils中的QueryRunner类
- 提交事务
- 若出现异常,在try-catch中rollback
- 关闭资源
- 数据库事务的代码示例
public void transaction(double accountA, double accountB){ Connection conn = null; PreparedStatement psA = null; PreparedStatement psB = null; String sqlA = "UPDATE testjob SET banlance = banlance - ? WHERE name = 'Tom'"; String sqlB = "UPDATE testjob SET banlance = banlance + ? WHERE name = 'Jerry'"; try { conn = JDBCTools.getConnection(); conn.setAutoCommit(false); psA = conn.prepareStatement(sqlA); psB = conn.prepareStatement(sqlB); psA.setObject(1, accountA); psB.setObject(1, accountB); psA.executeUpdate(); psB.executeUpdate(); conn.commit(); } catch (SQLException e) { e.printStackTrace(); try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } }finally { try { if(psA != null) psA.close(); if(psB != null) psB.close(); if(conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
- 数据库连接池
- 引入数据库连接池的好处在于:
- 对数据库连接这一宝贵资源,做到了重复利用,使用完无需关闭放回线程池就好
- 不用每次使用数据库连接,都要创建-使用-关闭,提高了程序响应速度
- 能够自由控制数据库连接的数量,便于管理连接
- 数据库连接池的使用
- C3P0:开源第三方提供,速度慢但是相对稳定
- DBCP:Apache提供的数据库连接池实现,快但不稳定
- 导入依赖
<dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency>
- 代码示例
@Test//将用户名、密码等属性写成一个配置文件 public void testDBCPOnFile(){ Properties pros = new Properties(); //InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream("DBCP.properties"); try { InputStream in = new FileInputStream(new File("src/main/resources/DBCP.properties")); pros.load(in); DataSource source = BasicDataSourceFactory.createDataSource(pros); Connection conn = source.getConnection(); System.out.println(conn); } catch (Exception e) { e.printStackTrace(); } }
- Druid:阿里提供的开源连接池,兼具DBCP、C3P0的优点
- 导入依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.9</version> </dependency>
- 代码示例
@Test public void testOnFile(){ try { Properties pros = new Properties(); InputStream in = new FileInputStream(new File("src/main/resources/Druid.properties")); pros.load(in); DataSource source = DruidDataSourceFactory.createDataSource(pros); Connection conn = source.getConnection(); System.out.println(conn); } catch (Exception e) { e.printStackTrace(); } }
- 引入数据库连接池的好处在于:
- DBUtils的使用
- 导入依赖
<dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.6</version> </dependency>
- 代码示例
@Test public void test1() { QueryRunner queryRunner = null; Connection connection = null; try { queryRunner = new QueryRunner(); connection = JDBCTools.getConnection(); String sql = "INSERT INTO testjob(id,name, banlance) VALUES(?,?,?)"; int insertCount = queryRunner.update(connection, sql, "2015211211", "gaaag", "1500.0"); System.out.println(sql); } catch (SQLException e) { e.printStackTrace(); } finally { JDBCTools.closeConnection(connection, null); } }
Java学习笔记——JDBC
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...