一、JDBC 简介
SUN 公司为了简化、统一数据库的操作,定义了一套 Java 操作数据库的规范,称之为 JDBC。JDBC 是一套接口,并不能真正操作数据库,而各个数据库的驱动是 JDBC 的实现,用来对数据库的操作。
JDBC 全称:Java Data Base Connectivity(Java 数据库连接),组成 JDBC 的两个包 java.sql、javax.sql。开发 JDBC 应用需要以上两个包的支持外,还需要导入相应的JDBC 的数据库实现(数据库驱动)。
二、使用 JDBC
1、加载驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
2、获取连接
String url = "jdbc:mysql://localhost:3306/mydb";
String user = "root";
String password = "root";
Connection con = DriverManager.getConnection(url, user, password);
3、获取向数据库发送 sql 语句的Statement 对象
Statement st = con.createStatement();
4、向数据库发送 sql 语句
String sql = "select * from account";
ResultSet rs = st.executeQuery(sql);
5、处理结果
while(rs.next()){
rs.getObject("name");
.......
}
6、释放资源
rs.close();
st.close();
con.close();
三、DriverManager
1、在实际开发中,我们不会使用通常不会使用上面的方法来注册驱动,原因有两个。第一,如果我们使用DriverManager.registerDriver 会导致在 DriverManager 中注册了两个 Driver 对象。查看 mysql 源码发现,当我们使用 new com.mysql.jdbc.Driver() 的时候,在 Driver 类中有一个静态代码块,在静态代码块中执行了将自己注册到 DriverManager 中的操作,之后我们在使用DriverManager.registerDriver 时,等于在内存中加载了两个 Driver。第二,我们在加载驱动的时候,已经将 mysql 的驱动写死在了程序中,导致我们这个程序在开发完以后不能更改数据库,只能使用 mysql。基于这两个原因,我们一般不会使用 DriverMagnaer.register 来注册驱动。
2、使用 Class.forName(driver) 来加载驱动,如果使用这种方式,那么在内存中只有一份驱动的字节码文件,并且要加载的驱动名 driver 我们可以写在配置文件中,更改配置文件我们就可以更改数据库。
3、数据库 URL
jdbc:mysql://localhost:3306/account?参数名:参数值(可以跟用户名、密码、字符编码。。)
| | | | |
协议 子协议 主机 端口 数据库
jdbc:mysql///account(简写,默认连接本机3306)
Oralce:jdbc:oralce:this:@localhost:1521:account
四、Connection
它用于代表数据库的连接,Connection 是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过 Connection 对象完成的。
createStatement() 创建数据库发送 sql 的 statement 对象
preparedStatement(sql) 创建向数据库发送预编译 sql 的 preparedStatement 对象
prpareCall(sql) 创建执行存储过程的 callableStatement 对象
setAutoCommit(boolean autoCommit) 设置事务是否自动提交
commit() 在连接上提交事务
rollback() 在连接上回滚事务
五、Statement
用于向数据库发送 sql 语句
executeQuery(String sql) 用于向数据库发送查询语句
executeUpdate(String sql) 用于向数据库发送更新语句 insert、update、delete
execute(String sql) 用于向数据库发送任意 sql 语句(在未知将要进行什么操作时使用)
addBatch(String sql) 把多条 sql 语句放到一个批处理中
executeBatch() 向数据库发送一批 sql 语句执行。
六、ResultSet
用于代表 sql 语句的执行结果。ResultSet 封装执行结果时,采用的是类似于表格的方式,ResultSet 对象中维护了一个指向表格数据行的游标,初始的时候游标位于结果集的第一行之前,调用 next() 方法可以使游标指向具体的数据行,进而调用方法获取该行的数据。在 RestultSet 获取数据的时候应该对应数据类型来获取,这样方便对 Bean 的封装。
next() 移动到下一行
previous() 移动到前一行
absolute(int row) 移动到指定行
beforeFirst() 移动到 ResultSet 的最前面
afterLast() 移动到 ResultSet 的最后面
七、释放资源
JDBC 程序运行完以后,切记要释放程序在运行过程中创建的那些与数据库进行交互的对象。特别是 Connection 对象,是非常稀有的资源,用完后要马上释放,如果 Connection 不能及时正确的关闭,极易导致系统的宕机。
- try {
- if (rs != null)
- rs.close();
- } catch (SQLException e) {
- } finally {
- try {
- if (st != null)
- st.close();
- } catch (SQLException e) {
- } finally {
- try {
- if (rs != null)
- con.close();
- } catch (SQLException e) {
- }
- }
- }
try { if (rs != null) rs.close(); } catch (SQLException e) { } finally { try { if (st != null) st.close(); } catch (SQLException e) { } finally { try { if (rs != null) con.close(); } catch (SQLException e) { } } }
八、泛型工厂实现 Service 层与 Dao 层解耦
我们在 Web 开发中通常使用三层架构的模式,在三层架构中,我们在 Web 层与用户交互,Service 处理业务逻辑, Dao 层实现与数据库的交互。这就避免不了 Service 层调用 Dao 层拿到数据库的数据再进行业务处理。如果我们直接将 Dao 层的实现引入到 Service 层,那么在将来更换数据库或者存储设备的时候将会带来极大的不便。于是我们使用泛型工厂模式对 Service 和 Dao 层实现解耦。
1、思路分析,我们要想使 Service 和 Dao 层实现解耦必须从 Dao 层抽出接口,无论是学生 Dao,还是书本 Dao……都是这个接口的实现类。然后我们创建一个工厂,这个工厂专门负责为 Service 层提供所需要的实现类,在工厂中根据 Service 传递进来的类,读取相应的配置文件,从而动态获取一个 Dao 实现类的实例。在 Service 层中,根据不同的业务需求,将想使用的 Dao 的字节码交给工厂,从工厂中获取 Dao,从而达到了 Service 层与 Dao 层解耦。在将来无论 Dao 层怎样更改,都不会影响业务逻辑层的实现。
2、编程实现:
Dao 层接口
- package cn.dk.dao;
- import cn.dk.domain.Student;
- public interface IStudentDao {
- void insert(Student stu);
- void delete(int id);
- void modify(Student stu);
- Student select(int id);
- }
package cn.dk.dao; import cn.dk.domain.Student; public interface IStudentDao { void insert(Student stu); void delete(int id); void modify(Student stu); Student select(int id); }
Dao 层实现
- package cn.dk.dao.impl;
- import cn.dk.dao.IStudentDao;
- import cn.dk.domain.Student;
- public class StudentDao implements IStudentDao {
- public void insert(Student stu) {
- }
- public void delete(int id) {
- };
- public void modify(Student stu) {
- }
- public Student select(int id) {
- return null;
- }
- }
package cn.dk.dao.impl; import cn.dk.dao.IStudentDao; import cn.dk.domain.Student; public class StudentDao implements IStudentDao { public void insert(Student stu) { } public void delete(int id) { }; public void modify(Student stu) { } public Student select(int id) { return null; } }
工厂
- package cn.dk.factory;
- import java.io.IOException;
- import java.io.InputStream;
- import java.util.Properties;
- public class DaoFactory {
- private DaoFactory() {
- }
- private static DaoFactory factory = new DaoFactory();
- private static Properties properties = new Properties();
- static {
- InputStream in = DaoFactory.class.getClassLoader().getResourceAsStream(
- "dao.properties");
- try {
- properties.load(in);
- } catch (IOException e) {
- throw new ExceptionInInitializerError(e);
- }
- }
- public static DaoFactory newInstance(){
- return factory;
- }
- public <T> T getDao(Class<T> daoClazz) {
- String simpleName = daoClazz.getSimpleName();
- String className = properties.getProperty(simpleName);
- try {
- return (T) Class.forName(className).newInstance();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- }
package cn.dk.factory; import java.io.IOException; import java.io.InputStream; import java.util.Properties; public class DaoFactory { private DaoFactory() { } private static DaoFactory factory = new DaoFactory(); private static Properties properties = new Properties(); static { InputStream in = DaoFactory.class.getClassLoader().getResourceAsStream( "dao.properties"); try { properties.load(in); } catch (IOException e) { throw new ExceptionInInitializerError(e); } } public static DaoFactory newInstance(){ return factory; } public <T> T getDao(Class<T> daoClazz) { String simpleName = daoClazz.getSimpleName(); String className = properties.getProperty(simpleName); try { return (T) Class.forName(className).newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } }
Service 引用
- DaoFactory factory = DaoFactory.newInstance();
- StudentDao dao = factory.getDao(StudentDao.class);
- dao.delete(1);
DaoFactory factory = DaoFactory.newInstance(); StudentDao dao = factory.getDao(StudentDao.class); dao.delete(1);
配置文件
- StudentDao=cn.dk.dao.impl.StudentDao
StudentDao=cn.dk.dao.impl.StudentDao
九、PreparedStatement 防止 sql 注入
如果我们使用 Statement 对象进行 sql 语句的操作,当用户传递用户名和密码等参数的时候,就必须将 sql 语句的字符串截开:
select * from users where username = '" + username + "' and password = '" + password + "';
这样一来,就有可能引发一种情况的 sql 注入问题,比如用户传递进用户名为:' or 1=1 or username=' 那么当程序执行的时候 sql 语句就成为:
select * from users where username = '' or 1=1 or username='' and password='......';
那么 1=1 的情况永远成立,由于是 or 逻辑或,那么前后两个条件即便是都不成立都没有问题。如果我们采用 PreparedStatemenet 时,用占位符 ? 号为参数占位。
String sql = "select * from users where username=? and password=?";
preparedStatement = con.propareStatement(sql);
preparedStatement.setString(1, username);
preparedStatement .setString(2, password);
rs = preparedStatement.executeQuery();
原理分析:
当我们拿到有占位符占位的不完整 sql 时,我们先对 sql 语句进行预编译。然后我们使用 set 方法对 ? 号占位符进行替换的时候,它会先将参数进行转义,从而避免了输入 sql 语法的参数。
preparedStatement 除了可以防止 sql 注入的问题,它还有一个更为有用的功能。preparedStatement 会对我们要执行的 sql 语句进行预编译。当我们发送一条 sql 语句的时候,数据库不会直接运行的,它会将我们输入的 sql 语句进行编译,编译完以后才能执行。我们在没有用 preparedStatement 之前,我们向数据库发送一条 sql 语句,数据库就编译一条,然后再执行一条,这样效率是比较低的。preparedStatement 先将 sql 语句预编译以后,发给数据库,数据库可以直接执行。
十、JDBC 处理大数据
在实际开发中,程序需要把大文本或二进制数据保存到数据库中。大数据分为:clob (存储文本)和 blob(存储二进制数据,图像、声音、二进制文件等)。
对于 mysql 而言,只有 blob 没有 clob,mysql 采用 Text 存储大文本 blob 存储二进制
Text 分为:tinytext、text、mediumtext、longtext
blob 分为:tinyblob、blob、mediumblob、lobgblob
| | | |
256 byte 64k 16mb 4gb
1、存储大文本
String sql = "insert into emploee (id, resume) values(?,?)";
preparedStatement.setChataceterStream(2, reader, length);
我们需要一个输入流和文件的长度
2、获取大文本
reader = resultSet.getCharacetStream(2);
reader = resultSet.getBlob(2).getCharacterStream();
String s = resultSet.getString(2);
存储:
- public class BusinessService {
- public static void main(String[] args) throws Exception {
- Class.forName("com.mysql.jdbc.Driver");
- String url = "jdbc:mysql://localhost:3306/mydb";
- String user = "root";
- String password = "root";
- Connection con = DriverManager.getConnection(url, user, password);
- String sql = "insert into emploee (name,resume) values(?,?)";
- PreparedStatement ps = con.prepareStatement(sql);
- String path = BusinessService.class.getResource("/resume.txt").getPath();
- File file = new File(path);
- FileReader reader = new FileReader(file);
- ps.setString(1, "aaa");
- ps.setCharacterStream(2, reader, (int)file.length());
- ps.executeUpdate();
- reader.close();
- ps.close();
- con.close();
- }
- }
public class BusinessService { public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/mydb"; String user = "root"; String password = "root"; Connection con = DriverManager.getConnection(url, user, password); String sql = "insert into emploee (name,resume) values(?,?)"; PreparedStatement ps = con.prepareStatement(sql); String path = BusinessService.class.getResource("/resume.txt").getPath(); File file = new File(path); FileReader reader = new FileReader(file); ps.setString(1, "aaa"); ps.setCharacterStream(2, reader, (int)file.length()); ps.executeUpdate(); reader.close(); ps.close(); con.close(); } }
读取:
- public class BusinessService {
- public static void main(String[] args) throws Exception {
- Class.forName("com.mysql.jdbc.Driver");
- String url = "jdbc:mysql://localhost:3306/mydb";
- String user = "root";
- String password = "root";
- Connection con = DriverManager.getConnection(url, user, password);
- String sql = "select resume from emploee";
- PreparedStatement ps = con.prepareStatement(sql);
- ResultSet rs = ps.executeQuery();
- File file = new File("d:\\1.txt");
- FileWriter fw = new FileWriter(file);
- Reader reader = null;
- while (rs.next()) {
- reader = rs.getCharacterStream("resume");
- char[] chs = new char[1024];
- int len = 0;
- while ((len = reader.read(chs)) != -1) {
- fw.write(chs, 0, len);
- }
- }
- fw.close();
- reader.close();
- }
- }
public class BusinessService { public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/mydb"; String user = "root"; String password = "root"; Connection con = DriverManager.getConnection(url, user, password); String sql = "select resume from emploee"; PreparedStatement ps = con.prepareStatement(sql); ResultSet rs = ps.executeQuery(); File file = new File("d:\\1.txt"); FileWriter fw = new FileWriter(file); Reader reader = null; while (rs.next()) { reader = rs.getCharacterStream("resume"); char[] chs = new char[1024]; int len = 0; while ((len = reader.read(chs)) != -1) { fw.write(chs, 0, len); } } fw.close(); reader.close(); } }
3、存储二进制数据
preparedStatement.setBinaryStream(2, inputStream, len);
4、读取二进制数据
inputStream = resultSet.getBinaryStream(2);
inputStream = resultSet.getBlob(2).getBinaryStream();
存储:
- public class BusinessService {
- public static void main(String[] args) throws Exception {
- Class.forName("com.mysql.jdbc.Driver");
- String url = "jdbc:mysql://localhost:3306/mydb";
- String user = "root";
- String password = "root";
- Connection con = DriverManager.getConnection(url, user, password);
- String sql = "insert into emploee (pic) value (?)";
- PreparedStatement ps = con.prepareStatement(sql);
- String path = BusinessService.class.getClassLoader().getResource("1.jpg").getPath();
- File file = new File(path);
- InputStream fs = new FileInputStream(file);
- ps.setBinaryStream(1, fs, (int)file.length());
- ps.executeUpdate();
- fs.close();
- }
- }
public class BusinessService { public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/mydb"; String user = "root"; String password = "root"; Connection con = DriverManager.getConnection(url, user, password); String sql = "insert into emploee (pic) value (?)"; PreparedStatement ps = con.prepareStatement(sql); String path = BusinessService.class.getClassLoader().getResource("1.jpg").getPath(); File file = new File(path); InputStream fs = new FileInputStream(file); ps.setBinaryStream(1, fs, (int)file.length()); ps.executeUpdate(); fs.close(); } }
读取:
- public class BusinessService {
- public static void main(String[] args) throws Exception {
- Class.forName("com.mysql.jdbc.Driver");
- String url = "jdbc:mysql://localhost:3306/mydb";
- String user = "root";
- String password = "root";
- Connection con = DriverManager.getConnection(url, user, password);
- String sql = "select pic from emploee";
- PreparedStatement ps = con.prepareStatement(sql);
- ResultSet rs = ps.executeQuery();
- InputStream in = null;
- while(rs.next()){
- in = rs.getBinaryStream("pic");
- }
- byte[] b = new byte[1024];
- int len = 0;
- FileOutputStream fs = new FileOutputStream("d:\\1.jpg");
- while((len=in.read(b)) != -1){
- fs.write(b, 0, len);
- }
- fs.close();
- in.close();
- }
- }
public class BusinessService { public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/mydb"; String user = "root"; String password = "root"; Connection con = DriverManager.getConnection(url, user, password); String sql = "select pic from emploee"; PreparedStatement ps = con.prepareStatement(sql); ResultSet rs = ps.executeQuery(); InputStream in = null; while(rs.next()){ in = rs.getBinaryStream("pic"); } byte[] b = new byte[1024]; int len = 0; FileOutputStream fs = new FileOutputStream("d:\\1.jpg"); while((len=in.read(b)) != -1){ fs.write(b, 0, len); } fs.close(); in.close(); } }
十一、使用 JDBC 进行批处理
当需要向数据库发送一批 sql 语句执行时,应避免向数据库一条条发送执行,应采用 JDBC 的批处理几只,以提升执行效率。实现批处理有两种方式,第一种方式:Statement.addBatch(sql) 将 sql 语句放入 list 集合中,当执行 executeBatch() 批处理命令时,将 sql 语句一起发给数据库执行。clearBatch() 方法清除批处理命令。这种方式进行批处理的优点:可以向数据库发送多条不同的 sql 语句。缺点:没有对 sql 语句预编译,当数据库发送多条相似的 sql 语句时需要重复写上很多代码。第二种方式是:preparedStatement.addBatch(); 这种方式的有点是:发送的是预编译后的 sql 语句,执行效率高。缺点是:只能应用在 sql 语句相同,但参数不同的批处理中。因此这种形式的批处理经常用在对同一个表中批量插入数据,或批量更新表的数据。