在Java语言中,有一个专门链接数据库的规范(JDBC),专门负责链接数据库进行数据操做。各个数据库提供商会根据这套规范(接口)编写相关的实现类,封装成一个 jar 包供用户下载使用。因此在进行编程时,须要将相应的 jar 包导入到工程文件下的 lib 目录下,并创建依赖。java
这里咱们使用的是 mysql 数据库。mysql
经过下述语句实现注册驱动,原理是这句语句会将 Driver.class 这份字节码加载到 JVM 中,而后 JVM 会执行该字节码的静态代码块,mysql 提供的这个驱动包中,Driver 的字节码内的静态代码块就完成了驱动对象的建立和注册。sql
1 //加载注册驱动 2 Class.forName("com.mysql.jdbc.Driver");
当咱们注册了驱动以后,能够经过 DriverManager 获取与数据库的链接,须要传入三个参数:数据库的地址,登陆用户名,登录密码。注意 Connection 和 DriverManager 类都是 java.sql 包下的,dbName 是数据库的名字。数据库
1 //链接数据库 2 Connection conn = DriverManager.getConnection(jdbc:mysql://localhost:3306/dbName", "root", "123");
验证已经获取链接,能够在 mysql 控制台,使用命令:show processlist 查看运行进程。编程
当获得与数据的链接以后,咱们还须要经过该链接得到一个语句对象,该语句对象内包含咱们要对数据库执行的操做,也就是 SQL 语句。咱们执行 SQL 语句通常使用语句对象的 executeUpdate 和 executeQuery 方法,前者用于 DDL 和 DML 操做,返回收影响的行数;后者用于执行 DQL 操做,返回结果集对象。具体使用方法以下浏览器
1 //语句对象的得到 2 Statement st = conn.createStatement(); 3 //执行语句,这里的语句用于建表 4 st.executeUpdate("create table t_student (id int primary key auto_increment, name varchar(20), age int)") 5 //释放资源,先进后出,须要处理异常 6 st.close(); 7 conn.close();
执行一次语句的步骤能够归为“贾(加)琏(连)欲(语)执(执)事(释)”这一句话,合在一块儿的源码为服务器
1 public class CreateTableTest { 2 public static void main(String[] args) throws ClassNotFoundException, SQLException { 3 Class.forName("com.mysql.jdbc.Driver"); 4 Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/jdbc_demo", "root", "1234"); 5 String sql = "create table t_student (id int primary key auto_increment, name varchar(20), age int)"; 6 7 Statement st = conn.createStatement(); 8 st.execute(sql); 9 10 st.close(); 11 conn.close(); 12 } 13 }
实际开发中,JavaWeb 开发代码通常分为三层,分层结构是 JavaWeb 开发中的一种设计思想,这样会让咱们开发井井有条,每一层只要完成对应的功能便可,使得项目便于开发和维护。工具
DAO : Data Access Object 数据访问对象
在实际开发中,能够按照下列结构进行设计性能
在 1.4 中示范了怎么链接数据库进行一次建表操做,实际上 DAO 中的实现类内就是一个个这样的方法,好比常见的增、删、改、查,若是每一个方法内都写这样一些列流程,会在后期维护中产生很大麻烦,假如链接密码改了,每一个方法都要修改,这显然不现实,因此咱们要利用工具类以及配置文档优化代码。测试
实现类中的方法应该专一于功能的实现,得到链接是该方法须要的一个结果,该方法并不关注这个过程,不该该放在方法内混淆语义。咱们能够把链接数据库的代码放在 JdbcUtils 这个工具类内,该类的成员都是类成员,而后实现类的方法中直接经过类调用 getConnection() 方法得到链接。同时,注册驱动这个步骤咱们只须要执行一次就够了,咱们能够把这个步骤放在工具类的静态代码块中,在该类初始化的时候会自动执行一次。
1 public class JdbcUtils { 2 3 private static String driverClassName = "com.mysql.jdbc.Driver"; 4 private static String url = "jdbc:mysql://127.0.0.1:3306/jdbc_demo"; 5 private static String username = "root"; 6 private static String password = "1234"; 7 8 static { 9 try { 10 Class.forName(driverClassName); 11 } catch (ClassNotFoundException e) { 12 e.printStackTrace(); 13 } 14 } 15 16 public static Connection getConnection() throws SQLException { 17 Connection conn = DriverManager.getConnection(url, username, password); 18 return conn; 19 } 20 }
关闭资源也是一块鸡肋代码,重复且冗长,未重构前每一个方法的关闭过程以下
1 //假设是查询方法,除了关闭链接、语句还要关闭结果集,每次关闭都须要异常处理 2 /* 3 Connection conn; 4 PreparedStatement ps; 5 ResultSet rs; 6 */ 7 try { 8 //方法须要实现的功能 9 return xxx; 10 } catch (Exception e) { 11 e.printStackTrace(); 12 } finally { 13 //try 代码块中的 return 语句必定会在 finally 执行完后执行,因此关闭系统资源放在 finally 中。先进后出 14 try { 15 if(rs != null) { 16 rs.close(); 17 } 18 } catch (SQLException e) { 19 e.printStackTrace(); 20 } finally { 21 try { 22 if(ps != null) { 23 ps.close(); 24 } 25 } catch (SQLException e) { 26 e.printStackTrace(); 27 } finally { 28 try { 29 if(conn != null) { 30 conn.close(); 31 } 32 } catch (SQLException e) { 33 e.printStackTrace(); 34 } 35 } 36 } 37 }
能够看到这一段关闭资源的方法很是冗长,且每一个方法都要关闭一遍。咱们能够把这个关闭的流程写进工具类内,实现类的方法只须要调用 JdbcUtils.close() 方法便可。
1 //Statement 是 PreparedStatement 的父类,这样不论是 Statement 或者 PreparedStatement 类型的均可以用这个方法关闭 2 //对于 DML 语句,没有 ResultSet 对象,能够第三个变量穿 null 便可 3 public static void close(Connection conn, Statement s, ResultSet rs) { 4 try { 5 if(rs != null) { 6 rs.close(); 7 } 8 } catch (Exception e) { 9 e.printStackTrace(); 10 } finally { 11 try { 12 if(s != null) { 13 s.close(); 14 } 15 } catch (Exception e) { 16 e.printStackTrace(); 17 } finally { 18 try { 19 if(conn != null) { 20 conn.close(); 21 } 22 } catch (Exception e) { 23 e.printStackTrace(); 24 } 25 } 26 } 27 }
把链接须要的这些信息放在代码块中仍是不太方便,若是用户修改了帐号密码,或者主机地址改变,都须要从新修改代码,这不太知足咱们日常应用的要求。因此咱们能够把这些信息放在一个 .properties 文件中,Java 程序直接去读取这些信息。那之后修改了帐户密码,只须要本身打开这个 .properties 文件修改相应的字段便可。
#key=value driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/jdbc_demo username=root password=1234
.properties 内存放了一些键值对,注意等号左右没有空格,结尾也没有标点,# 做为注释
那如何读取这些信息呢?
Java 中经过类加载器读取配置文件得到一个输入流,能够经过两种方法得到类加载器
1 //1.经过某一类的字节码实例能够获取 2 ClassLoader cl = Object.class.getContextClassLoader(); 3 //2.经过当前线程得到 4 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 5 6 //经过类加载器读取配置文件得到输入流 7 InputStream in = cl.getResourceAsStream("dp.properties");
那如何得到相应的信息呢?
咱们能够经过 Properties 对象得到相应的信息。Properties 类是 Map 抽象类下一个专门用于读取配置文件的类,将输入流加载进去,便可经过“key”得到“value”。
1 Properties p = new Properties(); 2 p.load(in); 3 System.out.println(p.getProperty("driverClassName"));
1 public class JdbcUtils { 2 3 private static Properties p = new Properties(); 4 5 static { 6 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 7 InputStream in = cl.getResourceAsStream("dp.properties"); 8 try { 9 p.load(in); 10 Class.forName(p.getProperty("driverClassName")); 11 } catch (Exception e) { 12 e.printStackTrace(); 13 } 14 } 15 16 public static Connection getConnection() throws SQLException { 17 Connection conn = DriverManager.getConnection(p.getProperty("url"), p.getProperty("username"), p.getProperty("password")); 18 return conn; 19 } 20 21 public static void close(Connection conn, Statement s, ResultSet rs) { 22 try { 23 if(rs != null) { 24 rs.close(); 25 } 26 } catch (Exception e) { 27 e.printStackTrace(); 28 } finally { 29 try { 30 if(s != null) { 31 s.close(); 32 } 33 } catch (Exception e) { 34 e.printStackTrace(); 35 } finally { 36 try { 37 if(conn != null) { 38 conn.close(); 39 } 40 } catch (Exception e) { 41 e.printStackTrace(); 42 } 43 } 44 } 45 } 46 }
按照上面的作法,每次用户进行一次操做,都会创建链接,接着执行完成后销毁链接。虽然每次链接、断开链接用时不长,但当用户数量上来意后,对系统资源的消耗就会变得很高。因而咱们引入链接池管理链接。链接池里面拥有必定数量的链接(通常5 - 10个),当经过链接池getConnection 时,链接池提供一个链接供方法使用,当使用完毕后方法执行链接的 close 方法,这个时候并非直接关闭链接,而是将链接返回给链接池。
在Java中,链接池使用 javax.sql.DataSource 接口来表示链接池。注意:DataSource 仅仅只是一个接口,由各大服务器厂商来实现。经常使用的 DataSource 的实现:
介绍 Druid 使用方法
1 //须要导入 Druid 的 jar 包 2 //方法一: 3 //1 建立链接池对象,不能使用 DataSource 类型,由于 setXxx 方法时 DruidDataSource 类独有的 4 DruidDataSource dds = new DruidDataSource(); 5 //2 设置链接数据库的信息 6 dds.setDriverClassName(p.getProperty("driverClassName")); 7 dds.setUrl(p.getProperty("url")); 8 dds.setUsername(p.getProperty("username")); 9 dds.setPassword(p.getProperty("password")); 10 dds.setMaxActive(10); //最大链接数 11 Connection conn = dds.getConnection(); 12 13 //方法二 14 //经过链接池工程得到链接池 15 //1 经过工厂的静态方法得到链接池,传入上文中的 Properties 对象做为参数,工程自动读取配置信息 16 DataSource ds = DruidDataSourceFactory.createDataSource(p); 17 //2 得到链接 18 Connection conn = ds.getConnection();
使用链接池后的工具类
1 public class JdbcUtils { 2 3 private static Properties p = new Properties(); 4 5 //用工厂建立链接池对象,工厂底层对 properties 直接读取,只需传入一个 Properties 对象 6 private static DataSource dds; 7 static { 8 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 9 InputStream in = cl.getResourceAsStream("dp.properties"); 10 try { 11 p.load(in); 12 dds = DruidDataSourceFactory.createDataSource(p); 13 } catch (Exception e) { 14 e.printStackTrace(); 15 } 16 } 17 18 public static Connection getConnection() throws SQLException { 19 return dds.getConnection(); 20 } 21 22 public static void close(Connection conn, Statement s, ResultSet rs) { 23 try { 24 if(rs != null) { 25 rs.close(); 26 } 27 } catch (Exception e) { 28 e.printStackTrace(); 29 } finally { 30 try { 31 if(s != null) { 32 s.close(); 33 } 34 } catch (Exception e) { 35 e.printStackTrace(); 36 } finally { 37 try { 38 if(conn != null) { 39 conn.close(); 40 } 41 } catch (Exception e) { 42 e.printStackTrace(); 43 } 44 } 45 } 46 } 47 }
JDBC 中是默认提交事务的,也就是每一条 Statement 执行后,自动提交事务。在实际业务中,咱们可能须要把多个操做看成一个原子性操做来进行,要么全作,要么所有作。要实现这个要求,咱们只须要把自动提交事务给关闭,在经过回滚(rollback)和提交(commit)来完成一次事务。
1 //银行转帐例子 2 public class TransactionTest { 3 @Test 4 public void testName() throws Exception { 5 Connection conn = null; 6 Statement st = null; 7 ResultSet rs = null; 8 try { 9 conn = DruidUtil.getConnection(); 10 //将事务设置为手动提交 11 conn.setAutoCommit(false); 12 13 st = conn.createStatement(); 14 // 1.检查张无忌的帐号余额是否大于等于1000. 15 rs = st.executeQuery("SELECT balance FROM account WHERE name = '张无忌' AND balance >=1000"); 16 if(!rs.next()) { 17 throw new RuntimeException("亲,您的帐户余额不够"); 18 } 19 // 余额>=1000:GOTO 2: 20 // 余额 <1000:提示:亲,你的余额不足. 21 // 2.在张无忌的帐号余额上减小1000. 22 st.executeUpdate("UPDATE account SET balance = balance-1000 WHERE name = '张无忌'"); 23 24 System.out.println(1/0); //制造异常 25 26 // 3.在赵敏的帐户余额尚增长1000. 27 st.executeUpdate("UPDATE account SET balance = balance+1000 WHERE name = '赵敏'"); 28 29 //提交事务 30 conn.commit(); 31 } catch (Exception e) { 32 e.printStackTrace(); 33 //回滚事务 34 conn.rollback(); 35 }finally { 36 DruidUtil.close(conn, st, rs); 37 } 38 } 39 } 40 //最终钱不会由于异常而变少