您的位置:首页 > 其它

JDBC入门知识

2015-09-22 13:14 405 查看
一、JDBC入门

1、什么是JDBC?

JDBC(Java DataBase Connectivity)就是Java数据库连接,说白了就是用Java语言操作数据库。原来我们操作数据库是在控制台使用SQL语句来操作数据库,JDBC是使用Java语言向数据库发送SQL语句。

2、JDBC的原理

早期SUN公司的天才们想编写一套可以连接天下所有数据库的API,但是当他们开始时发现这是不可能完成的任务,因为各个厂商的数据库差异太大了。后来SUN公司开始于数据库厂商们讨论,最终得出的结论是,由SUN提供一套访问数据库的规范(就是一组接口),并提供连接数据库的协议标准,然后个数据库厂商会遵循SUN的规范提供一套访问自己公司数据库服务的API。SUN公司提供的规范命名为JDBC,而各个厂商提供的遵循了JDBC规范的,可以访问自己数据库的API称之为驱动。



JDBC是接口,而JDBC驱动才是接口的实现,没有驱动无法完成数据库连接,每个数据库厂商都有自己的驱动,用来连接自己公司的数据库。

当然还有第三方公司专门为某一数据库提供驱动,这样的驱动往往不是开元免费的。

3、JDBC核心类(接口)介绍

JDBC的核心类有DriverManager、Connection、Statement和ResultSet。

DriverManager(驱动管理器)的作用有两个:

注册驱动:这可以让JDBC知道要使用的是哪个驱动。

获取Connection:如果可以获取到Connection,那么说明已经与数据库连接上了。

Connection对象表示链接,与数据库的通讯都通过这个对象展开:

Connection对象最重要的一个方法就是获取Statement对象。

Statement是用来向数据库发送SQL语句的,这样数据库就会执行发送过来的SQL语句:

void executeUpdate(String sql):执行更新操作(insert,update,delete等)。

ResultSet executeQuery(String sql):执行查询操作,数据库在执行查询后会返回查询结果,查询结果就是ResultSet。

ResultSet对象表示查询结果集,只有在执行查询操作后才会有结果集产生,结果集是一个二维表格。操作结果集要学习移动ResultSet内部的"行光标",以及获取当前行上每一列的数据:

boolean next():使"行光标"移动到下一行,并返回移动后的行是否存在。

xxx getXxx(int col):获取当前行指定列上的值,参数就是列数,列数从1开始,而不是0。

4、编写一个JDBC程序

下面开始编写第一个JDBC程序:

【1】、mysql数据库驱动jar包:mysql-connector-java-5.1.13-bin.jar

【2】、获取连接

获取连接需要两步,一是使用DriverManager注册驱动,二是使用DriverManager获取connection对象。

注册驱动

看清楚了!注册驱动只有一句话:Class.forName(" com.mysql.jdbc.Driver "),下面的内容都是对这句代码的解释。

DriverManager类的registerDriver()方法的参数是java.sql.Driver,但是java.sql.Driver是一个接口,实现类由mysql来提供,MySQL中java.sql.Driver接口的实现类为com.mysql.jdbc.Driver。

注册驱动的代码如下:DriverManager.registerDriver(new com.mysql.jdbc.Driver());

上面的编码虽然可以注册驱动,是出现硬编码(代码以来MySQL驱动jar包),如果将来想要链接Oracle 数据库,那么必须要修改代码。并且其实这种注册驱动的方式是注册了两次驱动。

JDBC中规定,驱动类被加载时,需要手动把自己注册到DriverManager中,下面我们来看看com.mysql.jdbc.Driver类的源码:

<span style="font-family:Arial;font-size:12px;">       public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
……
}</span>


com.mysql.jdbc.driver类中的static块会创建本类对象,并注册到DriverManager中。这说明主要去加载com.mysql.jdbc.Driver类,那么就会执行static块,从而也就把com.mysql.jdbc.driver注册到DriverManager中,所以把注册驱动类的代码改为加载驱动类。

Class.forName("com.mysql.jdbc.Dri
4000
ver");

【3】、获取链接的也只有一段代码:DriverManager.getConnection(url,username,password);其中username和password是登陆数据库的用户名和密码。

url相对复杂一点,它是用来找到要连接的数据库地址,下面是mysql的url:jdbc:mysql://localhost:3306/mydb.

JDBC规定url的格式有三部分组成,每个组成部分使用冒号分隔。

第一部分是jdbc,这是固定的;
第二部分是数据库名称,连接mysql数据库,第二部分就是mysql;
第三部分是由数据库厂商规定的,我们需要了解每个数据库厂商的要求,mysql的第三部分分别由服务器IP(localhost)、端口号(3306)以及DATABASE名称组成。

下面是获取连接的语句:

Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb","root","123");

还可以在URL中提供参数:

jdbc:mysql://localhost:3306/mydb?useUnicode=true & characterEncoding=utf8

useUnicode参数,指定这个数据库链接过程中,使用的是Unicode字节集;
characterEncoding参数,指定连接数据库的过程中,使用的是UTF8字节集编码。

【4】、获取Statement

在得到Connection之后,说明已经与数据库连接上了,下面是通过Connection获取Statement对象的代码:Statement stmt = con.createStatement();

Statement是用来项数据库发送SQL语句的。

【5】、发送SQL增、删、改语句

String sql = " INSERT INTO stu VALUES(' zhangsan ',' 123 ') ";

int m = stmt.executeUpdate(sql);

其中int类型的返回值,表示执行这条SQL语句所影响的行数,如果SQL语句执行失败,那么executeUpdate方法会抛出一个SQLException异常。

【6】、发送SQL查询语句

String sql = " SELECT * FROM stu ";

ResultSet rs = stmt.executeQuery(sql);

请注意,执行查询使用的不是executeUpdate方法,而是executeQuery方法。executeQuery方法返回的是ResultSet,ResultSet封装了查询结果,称之为结果集。

【7】、读取结果集中的数据

ResultSet就是一张二维的表格,它内部有一个行光标,光标默认的位置在第一行的上方,我们可以调用rs对象的next()方法把" 行光标 "向下移动一行,当第一次调用next方法时," 行光标 "就到了第一行记录的位置,这样可以使用ResultSet提供的getXxx(int col)方法获取指定类的数据了。

rs.next();

rs.getInt(1);

如果不能确定数据的类型,可以使用getObject(int col),或getString(int col)等方法。

【6】、关闭

与IO流一样,使用后的东西都需要关闭,关闭的顺序是先得到的先关闭,后得到的后关闭。

rs.close();

stmt.close();

con.close();

5、代码的规范化

所谓规范化代码,就是无论是否出现异常,都要关闭ResultSet、Statement、以及Connection。

<span style="font-family:Arial;font-size:12px;">public void query{
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try{
con = getConnection();
stmt = con.createStatement();
String sql = "SELECT * FROM user ";
rs = stmt.executeQuery(sql);
while(rs.next()){
String username = rs.getString(1);
String password = rs.getString(2);
}catch(exception e){
throw new RuntimeException(e);
}finally{
try{
if(rs!=null) rs.close();
if(stmt!=null) rs.close();
if(con!=null) rs.close();
}catch(SQLException e){}
}
}
}</span>


二、JDBC对象介绍

1、JDBC中的主要类(接口)

DriverManager

Connection

Statement

ResultSet

2、DriverManager

其实我们今后只需要会用DriverManager的getConnection()方法即可。

Class.forName(" com.mysql.jdbc.Driver ");

String url = " jdbc:mysql://localhost:3306/mydb ";

String username = " root ";

String password = " 123 ";

Connection con = DriverManager.getConnection(url,username,password);

注意,上面代码可能出现的两个异常:

ClassNotFoundException:这个异常是在第一句出现的,出现这个异常有两个可能,【1】没有给mysql的jar包,【2】类名写错了。

SQLException:这个异常出现在第5句,查看三个参数是否有问题。

对于DriverManager.registerDriver()方法了解即可,因为我们今后注册只会用到Class.forName(),而不会使用这个方法。

3、Connection

Connection最为重要的方法就是获取Statement对象:

Statement stmt = con.createStatement();

后面在学习ResultSet时,还要学习一下下面的方法:

Statement stmt = con.createStatement(int,int);

4、Statement

Statement最为重要的方法就是:

int executeUpdate(String sql):执行更新操作,即执行insert、update、delete语句,其实这个方法也可以执行create table、alter table、drop table等语句,但我们很少使用JDBC来执行这些语句。

ResultSet executeQuery(String sql):执行查询操作,返回ResultSet,即结果集。

Statement还有一个boolean execute()方法,可以用来执行增、删、改、查所有SQL语句。该方法返回布尔型值,表示SQL语句是否有结果集。

如果使用execute()方法执行的是更新语句,那么还要调用int getUpdateCount()来获取insert、update、delete语句所影响的行数。

如果使用execute()方法执行的是查询语句,那么还要调用ResultSet getResultSet()来获取select语句的查询结果。

5、ResultSet之滚动结果集(了解)

【1】ResultSet提供了一系列的方法来移动" 行光标 ":

void beforeFirst():把光标放到第一行的前面,这也是光标默认的位置;

void afterLast():把光标放到最后一行的后面;

boolean first():把光标放到第一行的位置上,返回值表示调控光标是否成功;

boolean last():把光标放到最后一行的位置上;

boolean isBeforeFirst():当前光标位置是否在第一行前面;

boolean isAfterLast():当前光标位置是否在最后一行的后面;

boolean isFirst():当前光标位置是否在第一行上;

boolean isLast():当前光标位置是否在最后一行上;

boolean previous():把光标向上挪一行;

boolean next():把光标向下挪一行;

boolean relative(int row):相对位移,当row为正数时,表示向下移动row行,为负数时表示向上移动row行;

boolean absolute(int row):绝对位移,把光标移动到指定的行上;

int getRow():返回当前光标所有行。

上面的方法分为两类,一类用来判断游标位置的,另一类用来移动游标位置的。

如果结果集是不可滚动的,那么只能使用next()方法来移动游标,而beforeFirst()、afterLast()、first()、last()、previous()、relative()方法都是不可用的!

结果集是否支持滚动,要从Connection类的CreateStatement()方法说起,也就是说创建Statement决定了ResultSet是否支持滚动。

createStatement(int resultSetType,int resultSetConcurrency):

resultSetType参数的可选值:

ResultSet.TYPE_FORWARD_ONLY:不可滚动结果集;
ResultSet.TYPE_SCROLL_INSENSTIVE:滚动结果集,但结果集数据不会跟随数据库变化;
ResultSet.TYPE_SCROLL_SENSITIVE:滚动结果集,结果集数据跟随数据库变化,但没有数据库驱动会支持它。

resultSetConcurrency参数的可选值:

CONCUR_READ_ONLY:结果集是只读的,不能通过结果集反向影响数据库;
CONCUR_UPDATABLE:结果集时可更新的,对结果集的更新可以反向影响数据库。

【2】获取结果集元数据:

得到元数据:rs.getMetaData(),返回值为ResultSetMetaData;
获取结果集列数:int getColumnCount();
获取指定列的名:String getColumnName();

【3】结果集特征:当使用Connection的createStatement时,已经确定了statement生成的结果集时什么特征。

是否可滚动;
是否敏感;
是否可更新;

createStatement()生成的结果集:不滚动、不敏感、不可更新。

6、ResultSet之获取列数据

可以通过next()方法使ResultSet游标向下移动,当游标移动到你需要的行时,就可以获取该行的数据了,ResultSet提供了一系列的获取数据的方法:

String getString(int columnIndex):获取指定列的String类型数据;
int getInt(int columnIndex):获取指定列的int类型数据;
double getDouble(int columnIndex):获取指定列的double类型数据;
boolean getDouble(int columnIndex):获取指定列的boolean类型数据;
Object getObject(int columnIndex):获取指定列的SObject类型数据;

上面方法中,参数columnIndex表示列的索引,列索引从1开始,而不是0,这一点与数组不同。如果你清楚当前列的数据类型,那么可以使用getInt()方法来获取,如果你不清楚类的类型,那么你应该使用getObject()方法来获取。

ResultSet还提供了一套通过列名来获取列数据的方法:

String getString(String columnIndex):获取名称为columnIndex的列的String类型数据;
int getInt(String columnIndex):获取名称为columnIndex的列的int类型数据;
double getDouble(String columnIndex):获取名称为columnIndex的列的double类型数据;
boolean getDouble(String columnIndex):获取名称为columnIndex的列的boolean类型数据;
Object getObject(String columnIndex):获取名称为columnIndex的列的SObject类型数据;

三、PreparedStatement

1、什么是SQL攻击

在需要用户输入的地方,用户输入的是SQL语句的片段,最终用户输入的SQL片段与我们DAO中写的SQL语句合成一个完整的SQL语句,

2、演示SQL攻击

首先我们创建一张表,用来存储用户的信息。

CREATE TABLE user(
uid	CHAR(32) PRIMARY KEY,
username	VARCHAR(30) UNIQUE KEY NOT NULL,
PASSWORD 	VARCHAR(30)
);

INSERT INTO user VALUES('U_1001', 'zs', 'zs');
SELECT * FROM user;


下面我们写一个login()方法:

public void login(String username, String password) {
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try {
con = JdbcUtils.getConnection();
stmt = con.createStatement();
String sql = "SELECT * FROM user WHERE " +
"username='" + username +
"' and password='" + password + "'";
rs = stmt.executeQuery(sql);
if(rs.next()) {
System.out.println("欢迎" + rs.getString("username"));
} else {
System.out.println("用户名或密码错误!");
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
JdbcUtils.close(con, stmt, rs);
}
}


下面我们调用这个方法的代码:

login("a' or 'a'='a", "a' or 'a'='a");


这个行当前回事我们登录成功!因为输入的用户名和密码是SQL片段,最终与我们的login()方法的SQL语句组合在一起。组合后的SQL语句:

SELECT * FROM tab_user WHERE username='a' or 'a'='a' and password='a' or 'a'='a'


3、防止SQL攻击

过滤用户输入的数据是否包含非法字符;
分步校验,想使用用户名查询用户,然后校验密码;
使用PreparedStatement;

4、PreparedStatement是什么?

PreparedStatement叫预编译声明。

PreparedStatement是Statement的子接口,可以使用PreparedStatement来代替Statement。

PreparedStatement的好处:

防止SQL攻击;
提高代码的可读性,以及可维护性;
提高效率;

5、PreparedStatement的使用

使用Connection的prepareStatement(String sql):即创建它时就让它与一条SQL模板绑定;
调用PreparedStatement的setXxx()系列方法为问号设置;
调用executeUpdate()或executeQuery()方法,注意调用无参数的方法;

String sql = "SELECT *FROM tab_stuent WHERE s.name=?";
PreparedStatement ps = con.prepareStatement(sql);
ps.setString(1,"s_1001");
ResultSet rs = ps.executeQuery();
rs.close();
ps.clearParameters();
<pre name="code" class="java">ps.setString(1,"s_1002");
ResultSet rs = ps.executeQuery();



在使用Connection创建PreparedStatement对象时需要给出一个sql模板,所谓SQL模板就是有?的SQL语句,其中?代表参数。

在得到PreparedStatement对象后,调用它的setXxx()方法为?赋值,这样就可以把模板变成一条完整的SQL语句,然后再调用PreparedStatement的executeQuery()方法获取ResultSet对象。

注意PreparedStatement对象独有的executeQuery()方法是没有参数的,而Statement对象的executeQuery()是需要参数的(SQL语句)。因为在创建PreparedStatement对象时已经与一条SQL模板绑定在一起了,所以调用它的executeQuery()和executeUpdate()方法时就不需要参数了。

PreparedStatement对象最大的好处是,重复使用同一模板,给与不同的参数来重复的使用它,这才是真正提高效率的原因。

所以,建议大家在今后的开发中,无论什么情况都去使用PreparedStatement,而不是使用Statement。

四、JdbcUtils工具类

1、JdbcUtils的作用:

你也看到了,连接数据库的四大参数:驱动类、URL、用户名、密码,这些参数斗鱼特定的数据库关联,如果将来想要改变数据库,那么就要去修改四大参数,为了不修改代码,我们写一个jdbcUtils类,让他从配置文件中读取配置参数,然后创建连接对象。

2、JdbcUtils代码:

JdbcUtils.java

public class JdbcUtils{
//配置文件路径
private static final String dbconfig = "dbconfig.properties";

//对应配置文件路径
private static Properties prop = new Properties();

//把配置文件内容加载到prop对象中。因为是放到static块中,所以加载操作只会在JdbcUtils类被加载时完成对配置文件的加载。
static{
try{
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(dbconfig);
prop.load(in);
Class.forName(prop.getProperty("driverClassName"));
}catch(Exception e){
throw new RuntimeException(e);
}
}

public static Connection(){
try{
return DriverManager.getConnection(prop.getProperty("url"),prop.getProperty("username"),prop.getProperty("password"));
}catch(Exception e){
throw new RuntimeException(e);
}
}
}


dbconfig.properties

<span style="color:#000000;">driverClassName=</span><span style="color:#2a0ff;">com.mysql.jdbc.Driver</span><p><span style="color:#000000;">url=</span><span style="color:#2a0ff;">jdbc:mysql://localhost:3306/mydb1?useUnicode=true&characterEncoding=UTF8</span></p><p><span style="color:#000000;">username=</span><span style="color:#2a0ff;">root</span></p><p><span style="color:#000000;">password=</span><span style="color:#2a0ff;">123</span></p>


五、时间类型

1、Java中的时间类型:

java.sql包下给出三个与数据库相关的日期时间类型,分别是:

Date:表示日期,只有年月日,没有时分秒,会丢失日期;
Time:表示时间,只有时分秒,没有年月日,会丢失日期;
Timestamp:表示时间戳,有年月日时分秒,以及毫秒;

这三个类都是java.util.Date的子类。

2、时间类型相互转换:

java.util.Date--->java.sql.Date、Time、Timestamp

把util包中的Date转换成毫秒值,使用毫秒值创建sql包中的Date、Time、Timestamp。

java.util.Date date = new java.util.Date();

long l = date.getTime();

java.sql.Date date = new java.sql.Date( l );

java.sql.Date、Time、Timestamp--->java.util.Date

因为java.sql.Date是java.util.Date的子类,所以这类转换不用处理。

六、大数据

1、什么是大数据?

所谓的大数据,就是大的字节数据,或大的字符数据。标准SQL中提供了如下类型来保存大数据的类型:

类型

长度

tinyblob

28--1B(256B)

blob

216-1B(64K)

mediumblob

224-1B(16M)

longblob

232-1B(4G)

tinyclob

28--1B(256B)

clob

216-1B(64K)

mediumclob

224-1B(16M)

longclob

232-1B(4G)

但是SQL中没有提供tinyclob、clob、mediumclob、longclob四种类型,而是使用如下四种类型来处理文本大数据:

类型

长度

tinytext

28--1B(256B)

text

216-1B(64K)

mediumtext

224-1B(16M)

longtext

232-1B(4G)

2、向数据库写数据

首先我们需要创建一张表,表中要有一个mediumblob类型的字段:

CREATE TABLE stu(id INT PRIMARY KEY AUTO_INCREMENT,
filename VARCHAR(100),
data MEDIUMBLOB
);


向数据库插入二进制数据需要使用PreparedStatement对象的setBinaryStream(int,InputStream)方法来完成:

con = JdbcUtils.getConnection();
String sql = "insert into stu(filename,data) values(?,?)";
pstmt = con.prepareStatement(sql);
pstmt.setString(1,"a.jpg");
//得到一个输入流对象
InputStream in = new FileInputStream("c\\:a.jpg")
//为第二个参数复制为流对象
pstmt.setBianryStream(2,in);
pstmt.executeUpdate();


读取二进制数据,需要在查询后使用ResultSet类的getBinaryStream()方法来获取流对象。也就是说PreparedStatement有setXXX(),那么ResultSet就有getXXX()。

con = JdbcUtils.getConnection();
String sql = "select * filename,data from tab_bin where id=?";
pstmt = con.prepareStatement(sql);
pstmt.setInt(1,1);
rs = pstmt.executeQuery();

String filename = rs.getString("filename");
OutputStream out = new FileOutputStream("f:\\"+filename);

InputStream in = rs.getBinaryStream("data");
IOUtils.copy(in,out);
out.close);


还有一种方法,就是 要把存储的数据包装成Blob类型,然后调用PreparedStatement的setBlob()方法来设置数据:

con = JdbcUtils.getConnection();
String sql = "insert into tab_bin(filename,data) values(?,?)":
pstmt = con.prepareStatement(sql);
pstmt.setString(1,"a.jpg");

File file = new File("f:\\a.jpg");
byte[] datas = FileUtils.getBytes(file);//获取文件中的数据
Blob blob = new SerialBlob(datas);//创建Blob对象
pstmt.setBlob(2,blob);//设置blob类型的参数
pstmt.executeUpdate();

<p><span style="color:#000000;">con = JdbcUtils.</span><span style="color:#000000;">getConnection</span><span style="color:#000000;">();</span></p><p><span style="color:#000000;">String sql = </span><span style="color:#2a0ff;">"select filename,data from tab_bin where id=?"</span><span style="color:#000000;">;</span></p><p><span style="color:#000000;">pstmt = con.prepareStatement(sql);</span></p><p><span style="color:#000000;">pstmt.setInt(1, 1);</span></p><p><span style="color:#000000;">rs = pstmt.executeQuery();</span></p><p><span style="color:#000000;">rs.next();</span></p><p></p><p><span style="color:#000000;">String filename = rs.getString(</span><span style="color:#2a0ff;">"filename"</span><span style="color:#000000;">);</span></p><p><span style="color:#000000;">File file = </span><span style="color:#7f055;">new</span><span style="color:#000000;"> File(</span><span style="color:#2a0ff;">"F:\\"</span><span style="color:#000000;"> + filename) ;</span></p><p><span style="color:#000000;">Blob blob = rs.getBlob(</span><span style="color:#2a0ff;">"data"</span><span style="color:#000000;">);</span></p><p><span style="color:#7f055;">byte</span><span style="color:#000000;">[] datas = blob.getBytes(0, (</span><span style="color:#7f055;">int</span><span style="color:#000000;">)file.length());</span></p><p><span style="color:#000000;">FileUtils.</span><span style="color:#000000;">writeByteArrayToFile</span><span style="color:#000000;">(file, datas);</span></p>


七、批处理

1、Statement批处理

批处理就是一批一批的处理,而不是一个一个的处理。

当你有10条SQL语句要执行时,一次想服务器发送一条SQL语句,这么做效率很差! 处理的方案就是批处理,即一次向服务器发送多条SQL语句,然后由服务器一次性处理。

批处理只针对增、删、改语句,对查询没什么意义。

可以多次调用Statement的addBatch(String sql)方法,把需要执行的所有SQL语句添加到一个" 批 "中,然后调用Statement类的executeBatch()方法来执行当前"批"中的语句。

for(int x=0;x<10;x++){
String number = "s_10"+x;
String name = "stu"+x;
int age = 20+x;
String gender = x%2?"male":"female";
String sql = "insert into stu values('"+number+"','"+name+"',"+age+",'"+gender+"')";
stmt.addBatch(sql);//stmt内部有一个集合,用来装载sql语句
}
stmt.executeBatch();


当执行了"批"之后,"批"中的SQL语句就会被清空!!也就是说连续两次调用executeBatch()相当于调用一次。

还可以在执行"批"之前,调用Statement 的 clearBatch() 的方法来清空" 批 "。

2、PreparedStatement批处理

PreparedStatement的批处理有所不同,因为每个PreparedStatement对象都绑定一条SQL模板。所以向PreparedStatement中添加的不是SQL语句,而是给"?"赋值。

con = JdbcUtils.getConnection();
String sql = "insert into stu values(?,?,?,?)";
pstmt = con.prepareStatement(sql);
for(int x;x<10;x++){
pstmt.setString(1,"s_10"+x);
pstmt.setString(2,"stu"+x);
pstmt.setInt(3,20+x);
pstmt.setString(4,x%2==0?"male":"female");
pstmt.addBatch();
}
pstmt.executeBatch();


八、事务

(一)、事务概述

1、什么是事务?

银行转账!张三转1000元到李四的账户,这其实需要两条SQL语句:给张三的账户减去1000元,给李四的账户加上1000元。

如果在第一条SQL语句执行成功后,在执行第二条SQL语句之前,程序中断(可能抛出某个异常,或其他原因),那么李四的账户没有加上1000元,但是张三的账户减去了1000元,这样肯定不行。

事物的多个操作,要么是完全成功,要么是完全失败,不可能出现成功一半的情况。

2、事务的四大特性

原子性(Atomicity):事务中所有操作是不可分隔的原子单位。事务要么完全成功,要么完全失败。

一致性(Consistency):事务执行后,数据库状态与其他业务规则保持一致。无论成功与否,参与转账的两个账户余额之和是不变的。

隔离性(Isolation):在并发操作中,不同事务之间应该隔离开来,是每个并发中的事务不会互相干扰。

持久性(Durability):一旦事务提交成功,事务中的所有数据操作都必须被持久化到数据库中,即使提交事务之后数据库马上崩溃,在数据库重启时,也必须通过某种机制恢复数据。

3、MySQL中的事务

在默认情况下,MySQL每执行一条SQL语句,都是一个单独的事务。如果需要在一个事务中包含多条SQL语句,那么需要开启事务和结束事务。

开启事务:start transction;
结束事务:commit 或 rollback;

在执行SQL语句之前,先执行start transction,这就开启了一个事务(事务起点),然后可以去执行多条SQL语句。最后结束事务,commit表示提交,即事务中的多条SQL语句所作出的影响会持久化到数据库中。或者,rollback表示回滚,即回滚到事务起点,之前的所有操作都被撤销。

(二)、JDBC事务

在JDBC中处理事务都是通过Connection完成的。

同一事务中所有的操作,都是使用同一个Connection对象!

1、JDBC中的事务

Connection的三个方法与事务相关:

setAutoCommit(boolean b):设置为是否为自动提交事务,如果true(默认值为true)表示自动提交,也就是每条执行的SQL语句都是一个单独的事务;如果设置为false,那么就相当于开启了事务了。

commit():提交结束事务。

rollback():回滚结束事务。

JDBC处理事务的代码格式:

try{

con.setAutoCommit(false);//开启事务

...

...

con.commit();//try的最后提交事务

}catch(){

con.rollback();//抛出异常后回滚事务

}

示例:

public void transfer(boolean b){
Connection con = null;
PreparedStatement pstmt = null;
try{
con = JdbcUtils.getConnection();

//设置为手动提交事务,即开启了事务。
con.setAutoCommit(false);

String sql = "update account set balance=balance+? where id=?";
pstmt = con.prepareStatement(sql);

//操作
pstmt.setDouble(1,-10000);
pstmt.setInt(2,1);
pstmt.executeUpdate();

//如果出现了异常就回滚事务。
if(b){
throw new Exception();
}

<pre name="code" class="java">      pstmt.setDouble(1,-10000);
pstmt.setInt(2,1);
pstmt.executeUpdate();
//提交事务
con.commit();
}catch(Exception e){

//回滚事务

if(con!=null){

try{

con.rollback();

}catch(SQLException ex){}

}

throw new RuntimeException();

}finally{

JdbcUtils.close(con,pstmt);

}

}


2、保存点

保存点是JDBC3.0的东西,当要求数据库服务器支持保存点方式的回滚。

检验数据库服务器是否支持保存点:

Boolean b = con.getMetaData().supportsSavepoints();

保存点的作用是允许事务回滚到指定的保存点位置。在事务中设置好保存点,然后回滚时可以选择回滚到指定的保存点,而不是回滚整个事务。注意:回滚到指定保存点并没有结束事务,只有回滚了整个事务才算结束事务

Connection类的设置保存点,以及回滚到指定保存点方法:

设置保存点:SavePoint setSavePoint();
回滚到指定保存点:void rollback(Savepoint);

(三)、事务隔离级别

1、事务的并发读问题

脏读:读取到另一个事务未提交数据;
不可重复读:两次读取不一致;
幻读(虚读):读到另一事务已提交数据;

2、并发事务问题

因并发事务导致的问题大致5类,其中两类是更新问题,三类是读问题。

脏读(dirty read):读取到另一个事务的未提交数据,即读取到了脏数据;
不可重复读(unrepeatable read):对同一记录的两次读取不一致,因为另一事物对该记录做了修改;
幻读(phantom read):对同一张表的两次查询不一致,因为另一事务插入了一条记录;

不可重复读和幻读的区别:

不可重复读是读取到了另一事务的更新;
幻读是读取到了另一事务的插入(MySQL中无法测试到幻读);

3、四大隔离级别

四个等级的事务隔离级别,在相同数据环境下,使用相同的数据输入执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同事务隔离级别能够解决的数据并发问题能力是不同的。

【1】SERIALIZABLE(串行化)

不会出现任何并发问题,因为它对同一数据的访问时串行的,非并发访问;
性能最差;

【2】REPEATABLE READ(可重复读)---- MySQL

防止脏读和不可重复读,不能处理幻读问题;
性能比SERIALIZABLE好;

【3】READ COMMITTED(读一提交数据)---- Oracle

防止脏读,没有处理不可重复读,也没有处理幻读;
性能比REPEATABLE READ好;

【4】READ UNCOMMITTED(读未提交数据)

可能出现任何事物并发问题;
性能最好;

4、MySQL的隔离级别

MySQL的隔离级别为Repeatable read,可以通过下面语句查看:

select @@tx_isolation

也可以通过下面语句来设置当前链接的隔离级别:

set transction isolationleve[ 4选1 ]

5、JDBC设置隔离级别

con.setTransctionIsolation(int level),参数可选值如下:

Connection.TRANSCTION_READ_UNCOMMITTED;
Connection.TRANSCTION_READ_COMMITTED;
Connection.TRANSCTION_REPEATABLE_READ;
Connection.TRANSCTION_SERIALIZABLE;

九、数据库连接池

(一)、数据库连接池

1、数据库连接池的概念

用池来管理Connection,这样可以重复使用Connection。有了池,所以我们就不用自己来创建Connection,而是通过池来获取Connection对象。当使用完Connection后,调用Connection的close()方法,把Connection归还给池。池就可以再利用这个Connection对象了。



2、JDBC数据库连接池接口(DataSource)

Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商可以让自己的连接池实现这个接口,这样应用程序可以方便的切换不同厂商的连接池。

3、自定义连接池(ItcastPool)

分析:ItcastPool需要有一个List,用来保存连接对象。在ItcastPool的构造器中创建5个连接对象放到List中!当有人调用了ItcastPool的getConnection()方法时,那么就从List拿出一个返回。当List中没有连接可用时,抛出异常。

我们需要对Connection的close()方法进行增强,所以我们需要自定义ItcastConnection类,对Connection进行装饰,即对close()方法进行增强。因为需要在调用close()方法把连接归还给池,所以ItcastConnection类需要拥有对池对象的引用,并且池还要提供归还的方法。



(二)、DBCP

1、什么是DBCP?

DBCP是Apache提供的一款开源免费的数据库连接池。

Hibernate3.0 之后不再对DBCP提供支持,因为Hibernate声明DBCP有致命的缺陷。

2、DBCP的使用

public void fun() throws SQLException{

BasicDataSource ds = new BasicDataSource();

ds.setUsername(" root ");

ds.setPassword(" 123 ");

ds.setUrl(" jdbc:mysql://localhost:3306/mydb ");

ds.setDriverClassName(" com.mysql.jdbc.Driver ");

ds.setMaxActive(20); //最大连接数

ds.setMaxIdle(10); //最大空闲连接数

ds.setInitialSize(10); //初始化连接数

ds.setMinIdle(2); //最小空闲连接数

ds.setMaxWaite(1000); //最大等待毫秒数

Connection con = ds.getConnection();

con.close();

}

3、DBCP的配置信息

基本配置

driverClassName=com.mysql.jdbc.Driver

url=jdbc:mysql://localhost:3306/mydb1

username=root

password=123

初始化池大小,即一开始池中就有10个连接对象(默认值为0)

initialSize=0

最大连接数,如果设置maxActive = 50时,池中最多可以有50个连接,当然这50个连接中包含被使用的和没被使用的(空闲)

maxActive=8

最大空闲连接数,默认值为8,如果设置负数,表示没有吸纳之

maxIdle=8

最小空闲连接数,默认值为0

minIdle=0

最大等待时间,默认值为-1,表示无限等待,不会抛出异常

maxWait=-1

连接属性,就是原来放在URL后面的参数,可以使用connectionProperties来指定,如果已经在URL后面指定了那么就不用再这里指定了。

useServerPrepstmts=true ---- MySQL开启预编译功能

cachePrepstmts=true ---- MySQL开启缓存PreparedStatement功能

prepStmtCacheSize=50 ---- 缓存preparedStatement的上限

preStmtCacheSqlLimit=300 ---- 当SQL模板大于300时,就不用缓存它

连接的默认提交方式,默认值为true

defaultAutoCommit=true

连接是否为只读连接。Connection有一对方法:setReadOnly(boolean b)和isReadOnly(),如果是只读连接,那么你只能用这个链接来查询,指定连接为只读是为了优化,与并发事务有关。

defaultReadOnly=false

指定事务的隔离级别

defaultTransctionIsolation=REPEATABLE_READ

(三)、C3P0

1、C3P0简介

C3P0也是开源免费的连接池!C3P0倍很多人看好!

2、C3P0使用

C3P0中的池类是ComboPooledDataSource

public void function()throws PropertyVetoException,SQLException{
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
ds.setUser("root");
ds.setPassword("123");
ds.setDriverClass("com.mysql.jdbc.Driver");

ds.setAcquireIncrement(5);//每次的增量为5
ds.setInitialPoolSize(20);//初始化连接数
ds.setMinPoolSize(2);//最小连接数
ds.setMaxPoolSize(50);//最大连接数

Connection  con = ds.getConnection();
con.close();
}


配置文件要求:

文件名称:必须叫c3p0-config.xml
文件位置:必须在src下

C3P0也可以指定配置文件,而且配置文件可以是properties,也可以是xml的。但是C3P0的配置文件名必须叫c3p0-config.xml,并在必须放在类路径下。

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb</property>
<pre name="code" class="html">
<property name="driverClass">com.mysql.jdbc.Driver</property>

<property name="user">root</property>

<property name="password">123</property>

<property name="acquireIncrement">3</property>

<property name="initialPoolSize">10</property>

<property name="minPoolSize">2</property>

<property name="maxPoolSize">10</property>

</difault-config>
<named-config name="oracle-donfig">
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb</property>

<pre name="code" class="html">         <property name="driverClass">com.mysql.jdbc.Driver</property>

<property name="user">root</property>

<property name="password">123</property>

<property name="acquireIncrement">3</property>

<property name="initialPoolSize">10</property>

<property name="minPoolSize">2</property>

<property name="maxPoolSize">10</property>

</named-config> </c3p0-config>




C3P0的配置文件中可以配置多个连接信息,可以给每个配置起个名字,这样可以方便通过配置名称来切换配置信息。上面文件中默认配置为mysql配置,名为oracle-config的配置也是mysql的配置。

<p><span style="color:#7f055;">//default默认配置
</span></p><p><span style="color:#7f055;">public</span><span style="color:#000000;"> </span><span style="color:#7f055;">void</span><span style="color:#000000;"> fun2() </span><span style="color:#7f055;">throws</span><span style="color:#000000;"> PropertyVetoException, SQLException {</span></p><p><span style="color:#000000;">   ComboPooledDataSource ds = </span><span style="color:#7f055;">new</span><span style="color:#000000;"> ComboPooledDataSource();</span></p><p><span style="color:#000000;">   Connection con = ds.getConnection();</span></p><p><span style="color:#000000;">   System.</span><span style="color:#00c0;">out</span><span style="color:#000000;">.println(con);</span></p><p><span style="color:#000000;">   con.close();</span></p><p><span style="color:#000000;">}</span></p>


<p><span style="color:#7f055;"><span style="color:#7f055;">//切换为oracle-config配置</span>
</span></p><p><span style="color:#7f055;">public</span><span style="color:#000000;"> </span><span style="color:#7f055;">void</span><span style="color:#000000;"> fun2() </span><span style="color:#7f055;">throws</span><span style="color:#000000;"> PropertyVetoException, SQLException {</span></p><p><span style="color:#000000;">   ComboPooledDataSource ds = </span><span style="color:#7f055;">new</span><span style="color:#000000;"> ComboPooledDataSource(</span><span style="color:#2a0ff;">"orcale-config"</span><span style="color:#000000;">)</span><span style="color:#000000;">;</span></p><p><span style="color:#000000;">   Connection con = ds.getConnection();</span></p><p><span style="color:#000000;">   System.</span><span style="color:#00c0;">out</span><span style="color:#000000;">.println(con);</span></p><p><span style="color:#000000;">   con.close();</span></p><p><span style="color:#000000;">}</span></p>


(四)Tomcat配置连接池


1、Tomcat配置JNDI资源

JNDI(Java Naming and Directory Interface),Java命名和目录接口。

JNDI的作用就是:在服务器上配置资源,然后通过统一的放置来获取配置资源。

下图是Tomcat文档提供的:



配置JNDI资源需要到<Context>元素中配置<Resource>子元素:

name:指定资源的名称,这个名称可以随便给,在获取资源时需要这个名称;
factory:用来创建资源的工厂,这个值基本是固定的。不用修改;
type:资源的类型,我们要给出的类型当然是连接池的类型;
bar:表示资源的属性,如果资源存在名为bar的属性,那么就配置bar的值。对于DBCP连接池而言,你需要配置的不是bar,因为没有bar属性,而应该去配置url、username等属性。

<p><Context>  </p><p>  <Resource name="mydbcp" </p><p>			type="org.apache.tomcat.dbcp.dbcp.BasicDataSource"</p><p>			factory="org.apache.naming.factory.BeanFactory"</p><p>			username="root" </p><p>			password="123" </p><p>			driverClassName="com.mysql.jdbc.Driver"    </p><p>			url="jdbc:mysql://127.0.0.1/mydb1"</p><p>			maxIdle="3"</p><p>			maxWait="5000"</p><p>			maxActive="5"</p><p>			initialSize="3"/></p><p></Context> 
</p>


<p><Context>  </p><p>  <Resource name="myc3p0" </p><p>			type="com.mchange.v2.c3p0.ComboPooledDataSource"</p><p>			factory="org.apache.naming.factory.BeanFactory"</p><p>			user="root" </p><p>			password="123" </p><p>			classDriver="com.mysql.jdbc.Driver"    </p><p>			jdbcUrl="jdbc:mysql://127.0.0.1/mydb1"</p><p>			maxPoolSize="20"</p><p>			minPoolSize ="5"</p><p>			initialPoolSize="10"</p><p>			acquireIncrement="2"/></p><p></Context> 
</p>


2、获取资源

配置资源的目的当然是为了获取资源,只要你启动了Tomcat,那么就可以在项目任何类中通过JDNI获取资源的方式来获取资源了。

下图是Tomcat文档提供的,与上面Tomcat提供的配置资源时对应的:



获取资源:

Context:javax.naming.Context;
InitialContext:javax.naming.InitialContext;
lookup(String str):获取资源的方法,其中" java:comp/env "是资源的入口(这是固定的名称),获取过来的还是一个Context,这说明需要在获取到的Context上进一步进行获取。" bean/MyBeanFactory "对应<Resource>中配置的name值,这回获取的就是资源对象了。

<p><span style="color:#000000;">Context cxt = </span><span style="color:#7f055;">new</span><span style="color:#000000;"> InitialContext(); </span></p><p><span style="color:#000000;">DataSource ds = (DataSource)cxt.lookup(</span><span style="color:#2a0ff;">"java:/comp/env/</span><span style="color:#2a0ff;">mydbcp</span><span style="color:#2a0ff;">"</span><span style="color:#000000;">);</span></p><p><span style="color:#000000;">Connection con = ds.getConnection();</span></p><p><span style="color:#000000;">System.</span><span style="color:#00c0;">out</span><span style="color:#000000;">.println(con);</span></p><p><span style="color:#000000;">con.close();</span></p>


<p><span style="color:#000000;">Context cxt = </span><span style="color:#7f055;">new</span><span style="color:#000000;"> InitialContext(); </span></p><p><span style="color:#000000;">Context </span><span style="color:#000000;">envC</span><span style="color:#000000;">xt =</span><span style="color:#000000;"> (Context)</span><span style="color:#000000;">cxt.lookup(</span><span style="color:#2a0ff;">"java:/comp/env"</span><span style="color:#000000;">);</span></p><p><span style="color:#000000;">DataSource ds = (DataSource)</span><span style="color:#000000;">env</span><span style="color:#000000;">.lookup(</span><span style="color:#2a0ff;">"</span><span style="color:#2a0ff;">mydbcp</span><span style="color:#2a0ff;">"</span><span style="color:#000000;">);</span></p><p><span style="color:#000000;">Connection con = ds.getConnection();</span></p><p><span style="color:#000000;">System.</span><span style="color:#00c0;">out</span><span style="color:#000000;">.println(con);</span></p><p></p><p><span style="color:#000000;">con.close();</span></p>


上面两种方式是相同的效果。

十、ThreadLocal

1、ThreadLocal API

ThreadLocal类只有三个方法:

void set(T value):保存值
T get():获取值
void remove():移除值

2、ThreadLocal的内部是Map

ThreadLocal内部其实是一个Map来保存数据。虽然在使用ThreadLocal时只给出了值,没有给出键,其实它内部使用了当前线程作为键。

class MyThreadLocal<T> {
private Map<Thread,T> map = new HashMap<Thread,T>();
public void set(T value) {
map.put(Thread.currentThread(), value);
}

public void remove() {
map.remove(Thread.currentThread());
}

public T get() {
return map.get(Thread.currentThread());
}
}




十一、DBUtils

1、DBUtils简介

DBUtils是Apache Commons组件的一员,开源免费。

DBUtils是对JDBC的简单封装,但它还是被很多公司使用。

DBUtils的jar包:dbtutils.jar

2、DBUtils主要类

DBUtils都是静态方法,一系列的close()方法。

QueryRunner:

update():执行insert、update、delete语句;
query():执行select语句;
batch():执行批处理;

3、QueryRunner之更新

QueryRunner的update()方法可以用来执行insert、update、delete语句。

【1】创建QueryRunner

构造器:queryRunner()

【2】update方法

int update(Connection con ,String sql ,Object... params)

还有另一种方式来使用QueryRunner。

【1】创建QueryRunner

构造器:QueryRunner( DataSource )

【2】update方法

int update(String sql ,Object... params)

这种方式在创建QueryRunner时传递了连接池对象,那么在调用update()方法时就不用再传递Connection了。

4、ResultSetHandler

我们知道在执行select语句之后得到的时ResultSet,然后我们还需要对ResultSet进行转换,得到我们最终想要的数据。你可以希望把ResultSet的数据放到一个List中,也可以是一个Map中,或者一个Bean中。

DBUtils 提供了一个接口ResultSetHandler,它就是用来ResultSet转换成目标类型的工具,你可以自己实现这个接口,把ResultSet转换成你想要的类型。

DBUtils提供了很多个ResultSetHandler接口的实现,这些实现基本够用,我们通常不用自己去实现ResultSetHandler。

MapHandler:单行处理器,把结果转为Map<String,Object>,其中列名为键;

MapListHandler:多行处理器,把结果转换为List<Map<String,Object>>;



BeanHandler:单行处理器,把结果集转换为Bean,该处理器需要Class参数,即Bean的类型;

BeanListHandler:多行处理器,把结果集转换为List<Bean>;



ColumnListHandler:多行单列处理器,把结果集转换为List<Object>,使用ColumnListHandler时需要指定某一列的名称或编号,例如new ColumnListHandler(" name ")表示把name列的数据放到List中;



ScalarHandler:单行单列处理器,把结果集转换为Object,一般用于聚集查询。



5、QueryRunner之查询

QueryRunner的查询方法是:

public <T> T query(String sql , ResultSetHandler<T> rh , Object... params);

public <T> T query(Connection con, String sql, ResultSetHandler<T> rh, Object… params)

query()方法会通过sql语句和params查询出ResultSet,然后通过rh把ResultSet转换成对应的类型再返回。

6、QueryRunner之批处理

QueryRunner还提供了批处理方法:batch()。

我们更新一行记录时需要指定一个Object[]为参数,如果是批处理,那么就要指定Object[][]为参数了。即多个Object[]就是Object[][]了,其中每个Object[]对应一行记录:

public void fun10() throws SQLException {
DataSource ds = JdbcUtils.getDataSource();
QueryRunner qr = new QueryRunner(ds);
String sql = "insert into tab_student values(?,?,?,?)";
Object[][] params = new Object[10][];[注意,这里是二维数组,这个二维数组有10个一维数组。]//表示 要插入10行记录
for(int i = 0; i < params.length; i++) {
params[i] = new Object[]{"S_300" + i, "name" + i, 30 + i, i%2==0?"男":"女"};
}
qr.batch[执行批处理](sql, params);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: