JDBC 概述(Java DataBase Connectivity)
持久化:把数据保存到可掉电式存储设备中以供以后使用。
持久化过程大多通过各种数据库完成。将内存中的数据存储在数据库中,也可以存储在磁盘文件、XML数据文件汇总。
JPA: JavaEE 的规范,Java persistence api:Java 持久化 API
为什么用 JDBC 规范?
加载数据库的驱动,调用驱动包获取数据库了解对象,驱动包由数据库自己去实现,sun 公司规定 JDBC 的接口和代码规范,从而去做到统一的 Java 代码去使用不同的数据库。只需要安装不同的数据库驱动。不管什么数据库,都用 JDBC 基准(工具和接口),去访问该数据库,但怎么访问是由数据库驱动包去实现。
程序员不需要对特定的数据库差异去做处理。Java 连接数据库的标准。
注意:在开发中,使用到的关于 JDBC 类的接口/对象 都是导入的 java.sql 或者 javax.sql 包里的类。
JDBC 步骤:
- 加载注册驱动
- 获取数据库连接对象
- 获取预编译语句对象
- 执行SQL命令
- 释放资源
加载注册驱动
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | @Testpublic void testConnection() throws Exception {
 
 Driver d = new Driver();
 DriverManager.deregisterDriver(d);
 
 
 
 
 Connection conn = DriverManager.getConnection("jdbc:mysql:///test", "root", "123456");
 System.out.println(conn);
 }
 
 | 
上述代码是有问题的:如果此时,我们更换数据库,就会出现编译错误。所以使用Class.forName(); 去加载驱动会更加适合
| 12
 
 | Class.forName("com.mysql.jdbc.Driver");
 
 | 
获取预编译语句对象
Connection对象 
- Statement createStatement()创建静态语句对象
- PrepareStatement prepareStatement(String sql)获取预编译语句对象
| 12
 
 | Statement st = conn.createStatement();
 
 | 
使用预编译的好处:


预编译语句对象优点:
- 缓存预编译SQL,更快的编译SQL(有些数据库不支持)
- 安全
执行SQL命令
Statement对象
- int executeUpdate(String sql)执行DML/DDL操作
- ResultSet executeQuery(String sql)执行DQL操作
关闭源
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | finally {try {
 if (st != null) {
 st.close();
 }
 } catch (SQLexception e) {
 e.printStackTrace();
 }
 try {
 if (conn != null) {
 conn.close();
 }
 } catch (SQLException e) {
 e.printStackTrace();
 }
 }
 
 | 
预编译语句对象

DQL
| 12
 3
 4
 5
 6
 7
 
 | ResultSet rs = ps.executeQuery();while (rs.next()) {
 String name = rs.getString("name");
 int age = rs.getInt("age");
 }
 
 rs.close();
 
 | 
Dao(Data Access Object)
一个表对应一个 DAO


夹在业务逻辑与数据库资源中间的接口。
我们把之前增删改查的操作,抽取到一个类中,以后想要做增删改查,直接调用这个类的方法即可。
为了规范抽取之后的方法名字等问题,会定义一个接口来规范增删改查。
这里只写了 impl.java 的查询,增删改都大同小异。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 
 | public Product get(Long id) {Connection conn = JDBCUtil.getConnection();
 PreparedStatement pStatement = null;
 ResultSet rs = null;
 String sql = "SELECT * FROM product WHERE id=?";
 try {
 pStatement = conn.prepareStatement(sql);
 pStatement.setObject(1, id);
 rs = pStatement.executeQuery();
 if (rs.next()) {
 Product p = new Product();
 p.setProductName(rs.getString("productName"));
 p.setSupplier(rs.getString("supplier"));
 p.setBrand(rs.getString("brand"));
 p.setCutoff(rs.getString("cutoff"));
 p.setCostPrice(rs.getString("costPrice"));
 p.setDir_id(rs.getLong("dir_id"));
 p.setSalePrice(rs.getDouble("salePrice"));
 p.setId(id);
 return p;
 }
 } catch (Exception e) {
 e.printStackTrace();
 } finally {
 JDBCUtil.close(new AutoCloseable[] {rs, pStatement, conn});
 }
 return null;
 }
 
 | 
Dao 接口中的方法
- 增:void save(String name, int age);
- void save(Student stu);Student 类是一个 javabean,描述数据库表结构的一个类,把这一类javabean 叫做domain,保存的时候 student 的对象 id 为 null
 
- 删:void delete(Long id)
- 改:void update(Long id, Student newStu; 修改的时候 Student 对象的 id 就是要改的那条记录
- 查单个: Student get(Long id);
- 差多个: List<Student> listAll();
规范 Dao 组件的类名和包名


Dao 规范的开发流程
- 创建连接数据库的配置文件

- 在工具类读入配置文件中的内容,创建连接对象
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 
 | package cn.lizhaoloveit.JDBC._Dao.util;
 import java.io.InputStream;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.SQLException;
 import java.util.Properties;
 
 
 public class JDBCUtil {
 private JDBCUtil() {}
 private static Properties ps = new Properties();
 
 static {
 
 
 InputStream in = Thread.currentThread()
 .getContextClassLoader()
 .getResourceAsStream("db.properties");
 
 try {
 ps.load(in);
 
 Class.forName(ps.getProperty("driverClassName"));
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 
 public static Connection getConnection() {
 try {
 return  DriverManager.getConnection(ps.getProperty("url"),
 ps.getProperty("username"),
 ps.getProperty("password"));
 } catch (SQLException e) {
 e.printStackTrace();
 }
 return null;
 }
 }
 
 | 
- 根据数据库的表创建javabean对象
- 在dao中写出增删改查接口

| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | public interface IProductDAO {
 void insert(Product p);
 
 void delete(Long id);
 
 void update(Product p);
 
 Product get(Long id);
 
 List<Product> gets();
 }
 
 | 
- 根据接口写出实现类并写测试类逐一进行测试(写一个方法测试一个)
优化
连接数据库因为每次都要写,所以应该在工具类中事先写好
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | private static String driverClassName = "com.mysql.jdbc.Driver";
 private static String url = "jdbc:mysql:///javaweb";
 private static String username = "root";
 private static String password = "123456";
 
 
 static {
 try {
 Class.forName(driverClassName);
 } catch (ClassNotFoundException e) {
 e.printStackTrace();
 }
 }
 
 
 public static Connection getConnection() {
 
 }
 
 | 

事务 (Transaction)
每个逻辑操作单元全部完成,数据的一致性可以保证,当单元中某一部分操作失败,整个事务应全部视为错误,所有从起点后的操作,全部回退到开始状态。
开启事务 -> commit 永久保存,或者 -> rollback 回退。
事务的 ACID 属性:
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation) 事务内部操作及使用的数据对并发的其他事务是隔离的。
- 持久性(Durability) 一个事务一旦被提交,它对数据库中的改变是永久的。
- 默认情况下,事务在执行完 DML 操作就自动提交。
- 查询操作是不需要事务的,但是一般在开发中都把查询放入事务
- MySQL中,只有 InnoDB 存储引擎支持事务,支持外键,MyISAM 不支持事务。
事务操作
模板:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | try {
 conn.setAutoCommit(false);
 操作1...
 操作2...
 操作N...
 
 conn.commit();
 } catch (Exception e) {
 
 
 conn.rollback();
 } finally {
 
 }
 
 | 
开启事务,就有一个事务锁的存在,必须要调用 commit 或者 rollback 才会释放
获取自动生成的主键
- 设置一个标记,告诉数据库,需要自动生成的主键,在获取预编译语句对象的时候,传递标记 Statement.RETURN_GENERATED_KEYS.

连接池
目前操作数据库的方式:
获取数据库连接对象,执行SQL,解析结果集,关闭资源(结果集对象,预编译语句对象,数据库连接对象)。
为什么必须使用数据库连接池?
JDBC 数据库连接 (Connection 对象) 使用 DriverManager 来获取,每次向数据库建立连接都要讲 Connection 加载到内存,再验证用户名密码。非常耗时,创建成本大。
对于每一次数据库连接,使用完后都要断开,否则程序出现异常未关闭,会导致数据库系统内存泄露。最终将导致重启数据库。
当前的操作数据库的方式,花费了大量资源和时间创建数据库连接对象,使用一次就关闭,没有充分使用数据库连接对象。
连接池是一个容器,装数据库连接对象。需要对连接对象进行管理,达到最高的效率。

Java 中使用 javax.sql.DataSource 接口表示连接池。
DataSource 和 Connection Pool 是同一个
DataSource 只是个接口,由各大服务器厂商实现(Tomcat,JBoss)
常用的DataSource实现:
- DBCP: Spring 框架推荐
- druid: 阿里巴巴的连接池 (号称Java 语言中性能最好的连接池)
获取 Connection 对象
未使用连接池:Connection conn = DriverManager.getConnection(url, username, password);
使用了连接池:dataSource对象.getConnection(); 返回连接对象已经是一个功能增强的连接对象了。
只要获取了 Connection 连接对象接下来操作是一模一样的。
释放 Connection对象(Connection对象.close()):
未使用连接池,是和数据库服务器断开,使用了连接池,还给连接池。

自己设计一个连接池:
DBCP 连接池
操作步骤:
- 导入相关 jar 包

- 获取连接池对象
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
 | private static Properties ps = new Properties();private static BasicDataSource dsSource = null;
 
 
 
 
 
 static {
 dsSource = new BasicDataSource();
 InputStream in = Thread.currentThread()
 .getContextClassLoader()
 .getResourceAsStream("db.properties");
 try {
 
 ps.load(in);
 
 
 dsSource.setDriverClassName(ps.getProperty("driverClassName"));
 dsSource.setUrl(ps.getProperty("url"));
 dsSource.setUsername(ps.getProperty("username"));
 dsSource.setPassword(ps.getProperty("password"));
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 
 | 
整个应用中只有一个连接池对象,所有客户端(多线程)都使用该连接池对象,而连接池对象是线程安全的,数据库连接对象是不安全的,多个线程不能使用一个数据库连接对象。所以在获取数据库连接对象时,每个线程都是拿的唯一的那个。
连接池的快捷创建方式:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 
 | private static Properties ps = new Properties();private static BasicDataSource dsSource = null;
 
 static {
 dsSource = new BasicDataSource();
 InputStream in = Thread.currentThread()
 .getContextClassLoader()
 .getResourceAsStream("db.properties");
 try {
 
 ps.load(in);
 
 
 
 ds = BasicDataSourceFactory.createDataSource(ps);
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 
 
 
 public static Connection getConnection() {
 try {
 Connection conn = dsSource.getConnection();
 System.out.println(conn);
 return conn;
 } catch (Exception e) {
 e.printStackTrace();
 }
 return null;
 }
 
 | 
Druid 连接池
druid: 是阿里巴巴研发出来的号称 Java 语言性能最高的连接池。
wiki地址
支持:MySQL、Oracle、DB1、sql Server等
支持对配置文件的密码加密
导入 jar druid-1.0.x.jar
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | private static Properties ps = new Properties();private static DataSource dsSource = null;
 
 
 static {
 InputStream in = Thread.currentThread()
 .getContextClassLoader()
 .getResourceAsStream("db.properties");
 try {
 
 ps.load(in);
 
 
 dsSource = DruidDataSourceFactory.createDataSource(ps);
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 
 | 
JDBC 的缺陷
- JDBC DAO中的代码是重复的。
- SQL 写死了,无法使用一套通用的代码针对不同类的不同条件的查询
- 结果集不通用。
查询获取结果集后,我们需要把结果集中的字段和值一一对应的存入我们的数据类中,此时,可以使用内省,通过字节码对象来生成字段名,获取结果集的值。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | while (rs.next()) {Product p = new Product();
 
 BeanInfo info = Introspector.getBeanInfo(Product.class, Object.class);
 PropertyDescriptor[] pds = info.getPropertyDescriptors();
 for (PropertyDescriptor pd : pds) {
 Object value = rs.getObject(pd.getName());
 Method m = pd.getWriteMethod();
 m.invoke(p, value);
 }
 list.add(p);
 }
 
 |