一,简介
JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序
二,编写JDBC程序
首先在mysql中创建一个库,并创建user表和插入表的数据,SQL脚本如下:
CREATE DATABASE jdbcLibrary CHARACTER SET utf8 COLLATE utf8_general_ci; USE jdbcLibrary; CREATE TABLE users( id INT PRIMARY KEY, uname VARCHAR(40), upwd VARCHAR(40) ); INSERT INTO users(id,uname,upwd) VALUES(1,'Zender','123456'); INSERT INTO users(id,uname,upwd) VALUES(2,'张三','123456'); INSERT INTO users(id,uname,upwd) VALUES(3,'李四','123456');
项目中导入驱动:
JDBCDemo代码如下:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class JDBCDemo { public static void main(String[] args) throws Exception { //要连接的数据库URL String url = "jdbc:mysql://localhost:3306/jdbcLibrary"; //用户名 String username = "root"; //密码 String password = "123456"; //1.加载驱动 Class.forName("com.mysql.jdbc.Driver"); //2.获取与数据库的链接 Connection conn = DriverManager.getConnection(url, username, password); //3.获取用于向数据库发送sql语句的statement Statement st = conn.createStatement(); String sql = "select id,uname,upwd from users"; //4.向数据库发sql,并获取代表结果集的resultset ResultSet rs = st.executeQuery(sql); //5.取出结果集的数据 while(rs.next()){ System.out.println("id=" + rs.getObject("id")); System.out.println("uname=" + rs.getObject("uname")); System.out.println("upwd=" + rs.getObject("upwd")); } //6.关闭链接,释放资源 rs.close(); st.close(); conn.close(); } }
运行结果:
1,DriverManager类
DriverManager用于加载驱动,并创建与数据库的链接。
API的常用方法:
DriverManager.registerDriver(new Driver())
DriverManager.getConnection(url, user, password)
注意:在实际开发中并不推荐采用registerDriver方法注册驱动。原因有二:
1、查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次,也就是在内存中会有两个Driver对象。
2、程序依赖mysql的api,脱离mysql的jar包,程序将无法编译,将来程序切换底层数据库将会非常麻烦。
推荐方式:Class.forName("com.mysql.jdbc.Driver")
采用此种方式不会导致驱动对象在内存中重复出现,并且采用此种方式,程序仅仅只需要一个字符串,不需要依赖具体的驱动,使程序的灵活性更高。
2,Connection类
用于代表数据库的链接,Connection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过connection对象完成的。
常用方法:
createStatement() |
创建向数据库发送sql的statement对象。 |
prepareStatement(sql) |
创建向数据库发送预编译sql的PrepareSatement对象。 |
prepareCall(sql) |
创建执行存储过程的callableStatement对象。 |
setAutoCommit(boolean autoCommit) |
设置事务是否自动提交。 |
commit() |
在链接上提交事务。 |
rollback() |
在此链接上回滚事务。 |
3,Statement类
用于向数据库发送SQL语句
常用方法:
executeQuery(String sql) |
用于向数据发送查询语句。 |
executeUpdate(String sql) |
用于向数据库发送insert、update或delete语句 |
execute(String sql) |
用于向数据库发送任意sql语句 |
addBatch(String sql) |
把多条sql语句放到一个批处理中。 |
executeBatch() |
向数据库发送一批sql语句执行。 |
4,ResultSet类
该类代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next() 方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。
常用方法:
获取任意类型的数据:
getObject(int index)
getObject(string columnName)
获取指定类型的数据:
getString(int index)
getString(String columnName)
next() |
移动到下一行 |
Previous() |
移动到前一行 |
absolute(int row) |
移动到指定行 |
beforeFirst() |
移动resultSet的最前面。 |
afterLast() |
移动到resultSet的最后面。 |
二,数据库的CRUD
创建一个db.properties文件用于存放MySQL数据库的连接信息,代码如下所示:
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/jdbcLibrary username=root password=123456 |
编写一个JdbcUtils工具类,用于连接数据库,获取数据库连接和释放数据库连接,代码如下:
import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class JdbcUtils { private static String driver = null; private static String url = null; private static String username = null; private static String password = null; static{ try{ //读取db.properties文件中的数据库连接信息 InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties"); Properties prop = new Properties(); prop.load(in); //获取数据库连接驱动 driver = prop.getProperty("driver"); //获取数据库连接URL地址 url = prop.getProperty("url"); //获取数据库连接用户名 username = prop.getProperty("username"); //获取数据库连接密码 password = prop.getProperty("password"); //加载数据库驱动 Class.forName(driver); }catch (Exception e) { throw new ExceptionInInitializerError(e); } } /** * * @方法名: getConnection * @描述: 获取数据库连接对象 * @return * @throws SQLException * @创建人 Zender */ public static Connection getConnection() throws SQLException{ return DriverManager.getConnection(url, username,password); } /** * * @方法名: release * @描述: 释放资源 * @param conn * @param st * @param rs * @创建人 Zender */ public static void release(Connection conn,Statement st,ResultSet rs){ try{ if(rs!=null){ rs.close(); rs = null; } if(st!=null){ st.close(); st = null; } if(conn!=null){ conn.close(); } }catch (Exception e) { e.printStackTrace(); } } }
JDBCDemo代码如下:
package com.zender; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import org.junit.Test; import com.zender.util.JdbcUtils; public class JDBCDemo { private Connection conn = null; private PreparedStatement st = null; private ResultSet rs = null; @Test public void insertUser(){ try{ //获取一个数据库连接 conn = JdbcUtils.getConnection(); //构建执行的SQL,SQL中的参数使用?作为占位符 String sql = "INSERT INTO users(id,uname,upwd) VALUES(?,?,?);"; //获取prepareStatement对象 st = conn.prepareStatement(sql); st.setInt(1, 6); st.setString(2, "Zender"); st.setString(3, "123456"); //执行插入操作,executeUpdate方法返回成功的条数 int num = st.executeUpdate(); if(num>0){ System.out.println("插入成功!!"); } }catch (Exception e) { e.printStackTrace(); }finally{ //SQL执行完成之后释放相关资源 JdbcUtils.release(conn, st, rs); } } @Test public void deleteUser(){ try{ conn = JdbcUtils.getConnection(); String sql = "delete from users where id=4"; st = conn.prepareStatement(sql); int num = st.executeUpdate(); if(num>0){ System.out.println("删除成功!!"); } }catch (Exception e) { e.printStackTrace(); }finally{ JdbcUtils.release(conn, st, rs); } } @Test public void deleteUpdate(){ try{ conn = JdbcUtils.getConnection(); String sql = "update users set uname='修改的Name', upwd='123' where id=1"; st = conn.prepareStatement(sql); int num = st.executeUpdate(); if(num>0){ System.out.println("更新成功!!"); } }catch (Exception e) { e.printStackTrace(); }finally{ JdbcUtils.release(conn, st, rs); } } @Test public void findUser(){ try{ conn = JdbcUtils.getConnection(); String sql = "select * from users where id=3"; st = conn.prepareStatement(sql); rs = st.executeQuery(); if(rs.next()){ System.out.println(rs.getString("id")); System.out.println(rs.getString("uname")); System.out.println(rs.getString("upwd")); } }catch (Exception e) { e.printStackTrace(); }finally{ JdbcUtils.release(conn, st, rs); } } }
这里使用了PreperedStatement,该类是Statement的子类,PreperedStatement可以避免SQL注入的问题,并且PreperedStatement对于sql中的参数,允许使用占位符的形式进行替换。
三,事务
事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。
例如:模拟银行转账,User1向User2转账
数据库中创建名字为account的table:
JDBCDemo代码如下:
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import org.junit.Test; import com.zender.util.JdbcUtils; public class JDBCDemo { private Connection conn = null; private PreparedStatement st = null; private ResultSet rs = null; @Test public void TransactionDemo(){ try{ //获取一个数据库连接 conn = JdbcUtils.getConnection(); //通知数据库开启事务 conn.setAutoCommit(false); //构建执行的SQL String sql = "update account set money=money-100 where id=1"; //获取prepareStatement对象 st = conn.prepareStatement(sql); //执行修改操作 st.executeUpdate(); sql = "update account set money=money+100 where id=2"; st = conn.prepareStatement(sql); st.executeUpdate(); //提交事务 conn.commit(); }catch (Exception e) { //出现异常回滚事务 e.printStackTrace(); }finally{ //释放资源 JdbcUtils.release(conn, st, rs); } } }
运行以上代码,数据库数据如下:
现在修改JDBCDemo代码,让代码在执行中途出错,导致有一部分SQL执行失败后,让数据库自动回滚事务,代码如下:
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import org.junit.Test; import com.zender.util.JdbcUtils; public class JDBCDemo { private Connection conn = null; private PreparedStatement st = null; private ResultSet rs = null; @Test public void TransactionDemo(){ try{ //获取一个数据库连接 conn = JdbcUtils.getConnection(); //通知数据库开启事务 conn.setAutoCommit(false); //构建执行的SQL String sql = "update account set money=money-100 where id=1"; //获取prepareStatement对象 st = conn.prepareStatement(sql); //执行修改操作 st.executeUpdate(); //出错代码 int x = 1/0; sql = "update account set money=money+100 where id=2"; st = conn.prepareStatement(sql); st.executeUpdate(); //提交事务 conn.commit(); }catch (Exception e) { //出现异常回滚事务 e.printStackTrace(); }finally{ //释放资源 JdbcUtils.release(conn, st, rs); } } }
运行以上代码,控制台报错:
数据库数据如下:
四,事务的四大特性
原子性(Atomicity) |
原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。比如在同一个事务中的SQL语句,要么全部执行成功,要么全部执行失败。 |
一致性(Consistency) |
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。以转账为例子,user1向user2转账,假设转账之前这两个用户的钱加起来总共是200,那么user1向user2转账之后,不管这两个账户怎么转,user1用户的钱和user2用户的钱加起来的总额还是200,这个就是事务的一致性。 |
隔离性(Isolation) |
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。 |
持久性(Durability) |
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响 事务的四大特性中最麻烦的是隔离性,下面重点介绍一下事务的隔离级别 |
五、事务的隔离级别
作用:
在多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。
不考事务的虑隔离性,可能出现以下问题:
1,脏读
脏读指一个事务读取了另外一个事务未提交的数据。
例如:
假设user1向user2转帐100元,对应sql语句如下所示:
SQL1:update account set money=money+100 where name='user2'
SQL2:update account set money=money-100 where name='user1'
当SQL1执行完,SQL2还没执行(user1未提交时),如果此时user2查询自己的帐户,就会发现自己多了100元钱。如果user1等user2走后再回滚,user2回来再次查询时候就会少100元。
2,不可重复读
不可重复读指在一个事务内读取表中的某一行数据,多次读取结果不同。
例如:
1,首先银行查询用户user1的余额,第一次查询结果为100元。
2,user1来到银行查询自己余额,查询的余额为100元,然后user1向账户中存入了500元并提交。
3,银行接着又进行了一次查询,此时A帐户为600元了。银行两次查询不一致,可能就会很困惑,不知道哪次查询是准的。
3,虚读(幻读)
虚读(幻读)是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
例如:
user1去银行存款500元(事务没有提交),这时候银行做总存款统计,所有用户存款为1000元,然后user1这个时候提交事务,这时银行再统计发现帐户钱多了500元,造成虚读。可能就会很困惑,不知道哪次查询是准的。