持久化(persistence):对象在内存中建立后,不能永久存在。把对象永久的保存起来就是持久化的过程。而持久化的实现过程大多经过各类关系数据库来完成。java
持久化的主要应用是将内存中的数据存储在关系型数据库中,固然也能够存储在磁盘文件、XML数据文件中。mysql
JDBC直接访问数据库。sql
第三方O/R工具,如Hibernate,mybatis。这些工具都是对JDBC的封装。 数据库
JDBC(Java Datebase Connectivity)是一个独立于特定数据库管理系统、通用的sql数据库存取和操做的公共接口。它是JAVA语言访问数据库的一种标准。 缓存
Java.sql.Driver接口是全部JDBC驱动程序须要实现的接口。这个接口是提供给数据库厂商使用的,不一样数据库厂商提供不用的实现。安全
在程序中不须要直接去访问实现了Driver接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现。性能优化
DriverManager类,用来建立链接,它自己就是一个建立Connection的工厂,设计的时候使用的就是Factory模式,给各数据库厂商提供接口,各数据库厂商须要实现它;服务器
Connection接口,根据提供的不一样驱动产生不一样的链接;mybatis
Statement接口,用来发送SQL语句;并发
Resultset接口,用来接收查询语句返回的查询结果。
1.注册加载一个驱动
2.建立数据库链接(Connection)
3.建立statement,发送sql语句
4.执行sql语句
5.处理sql结果
6.关闭statement和connection
加载 JDBC 驱动需调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名:
1
|
Class.forName(driver);
|
如:
注册MYSQL数据库驱动器
1
|
Class.forName(
"com.mysql.jdbc.Driver"
);
|
注册ORACLE数据库驱动器
1
|
Class.forName(
"oracle.jdbc.driver.OracleDriver"
);
|
能够调用 DriverManager 类的 getConnection(…….) 方法创建到数据库的链接:
1
|
Connection conn = DriverManager.getConnection(url,uid,pwd);
|
JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器经过这个 URL 选择正确的驱动程序,从而创建到数据库的链接。
JDBC URL的标准由三部分组成,各部分间用冒号“:”分隔。
JDBC URL格式:
1
|
协议:<子协议>:<子名称>
|
说明:
协议:JDBC URL中的协议老是jdbc
子协议:子协议用于标识一个数据库驱动程序
子名称:一种标识数据库的方法。子名称能够依不一样的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息
jdbc:<子协议>:<子名称>:是一个JNI方式的命名
注:JNI是Java Native Interface的缩写。从Java 1.1开始,Java Native Interface (JNI)标准成为java平台的一部分,它容许Java代码和其余语言写的代码进行交互。
如:
mysql的JDBC URL: jdbc:mysql://localhost:3306/mydbname
oracle的JDBC URL: jdbc:oracle:thin: @localhost :1521:mydbname
数据库链接被用于向数据库服务器发送命令和 SQL 语句,在链接创建后,须要对数据库进行访问,执行 sql 语句。
在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不一样方式:
Statement
PrepatedStatement
CallableStatement
Statement对象用于执行静态的 SQL 语句,而且返回执行结果。
经过调用 Connection 对象的 createStatement 方法建立该对象:
1
|
Statement sm = conn.createStatement();
|
Statement 接口中定义了下列方法用于执行 SQL 语句:
sm.executeQuery(sql); // 执行数据查询语句(select)
sm.executeUpdate(sql); // 执行数据更新语句(delete、update、insert、drop等)
PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句。
能够经过调用 Connection 对象的 preparedStatement() 方法获取 PreparedStatement 对象:
1
2
3
4
5
6
7
|
String sql =
"INSERT INTO user (id,name) VALUES (?,?)"
;
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(
1
,
1
);
ps.setString(
2
,
"admin"
);
ResultSet rs = ps.executeQuery();
// 查询
int
c = ps.executeUpdate();
// 更新
|
PreparedStatement 对象所表明的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXXX() 方法来设置这些参数。 setXXX() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值。
(1)使用PreparedStatement,代码的可读性和可维护性比Statement高。
(2)PreparedStatement 能最大可能提升性能。
DBServer会对预编译语句提供性能优化。由于预编译语句有可能被重复调用,因此语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不须要编译,只要将参数直接传入编译过的语句执行代码中就会获得执行。
在statement语句中,即便是相同操做但由于数据内容不同,因此整个语句自己不能匹配,没有缓存语句的意义。事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
(3)PreparedStatement能保证安全性,但 Statement有sql注入等安全问题。
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,从而利用系统的 SQL 引擎完成恶意行为的作法。以下代码:
1
2
3
|
String username=
"a' or 1=1 or 1='"
;
String psw=
"b"
;
String sql =
"select count(*) from t_user where username='"
+username+
"' and psw='"
+psw+
"'"
;
|
sql语句以下:
1
|
select
count
(*)
from
t_user
where
username=
'a'
or
1=1
or
1=
''
and
psw=
'b'
|
用 PreparedStatement 取代 Statement 就能够解决。
当不直接使用SQL语句,而是调用数据库中的存储过程时,要用到Callable Statement。
CallabelStatement从PreparedStatement继承。
例如:
1
2
3
4
5
6
7
|
String sql =
"{call insert_users(?,?)}"
;
// 调用存储过程
CallableStatement st = conn.prepareCall(sql);
st.setInt(
1
,
1
);
st.setString(
2
,
"admin"
);
// 在此 CallableStatement对象中执行 SQL 语句,该语句能够是任何种类的 SQL 语句。
st.execute();
|
查询语句,返回记录集ResultSet。
更新语句,返回数字,表示该更新影响的记录数。
ResultSet:
ResultSet 对象以逻辑表格的形式封装了执行数据库操做的结果集,ResultSet 接口由数据库厂商实现。
ResultSet 接口的经常使用方法:
next():将游标日后移动一行,若是成功返回true;不然返回false。ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行以前,能够经过 ResultSet 对象的 next() 方法移动到下一行。
getXxx(String name):返回当前游标下某个字段的值。如:getInt("id")或getSting("name")。
rs.close();
ps.close(); 或者 stat.close();
conn.close();
通常是在finally里面进行释放资源。
在数据库中,所谓事务是指一组逻辑操做单元,使数据从一种状态变换到另外一种状态。
为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它所有完成时,数据的一致性能够保持,而当这个单元中的一部分操做失败,整个事务应所有视为错误,全部从起始点之后的操做应所有回退到开始状态。
事务的操做:先定义开始一个事务,而后对数据做修改操做,这时若是提交(COMMIT),这些修改就永久地保存下来,若是回退(ROLLBACK),数据库管理系统将放弃您所做的全部修改而回到开始事务时的状态。
2.1 原子性(Atomicity)
原子性是指事务是一个不可分割的工做单位,事务中的操做要么都发生,要么都不发生。
2.2 一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另一个一致性状态。(数据不被破坏)
2.3 隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其余事务干扰,即一个事务内部的操做及使用的数据对并发的其余事务是隔离的,并发执行的各个事务之间不能互相干扰。
2.4 持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其余操做和数据库故障不该该对其有任何影响。
在JDBC中,事务默认是自动提交的,每次执行一个 SQL 语句时,若是执行成功,就会向数据库自动提交,而不能回滚。
为了让多个 SQL 语句做为一个事务执行,需调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务:
1
|
conn.setAutoCommit(
false
);
|
在全部的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
1
|
conn.commit();
|
在出现异常时,调用 rollback(); 方法回滚事务,通常再catch模块中执行回滚操做。
1
|
conn.rollback();
|
能够经过Connection的getAutoCommit()方法来得到当前事务的提交方式。
注意:在MySQL中的数据库存储引擎InnoDB支持事务,MyISAM不支持事务。
当须要批量插入或者更新记录时。能够采用Java的批量更新机制,这一机制容许多条语句一次性提交给数据库批量处理。一般状况下比单独提交处理更有效率。
JDBC的批量处理语句包括下面两个方法:
addBatch(String):添加须要批量处理的SQL语句或是参数;
executeBatch();执行批量处理语句;
一般咱们会遇到两种批量执行SQL语句的状况:
多条SQL语句的批量处理;
一个SQL语句的批量传参;
1
2
3
4
5
6
7
8
|
Statement sm = conn.createStatement();
sm.addBatch(sql1);
sm.addBatch(sql2);
...
//批量处理
sm.executeBatch()
//清除sm中积攒的参数列表
sm.clearBatch();
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
PreparedStatement ps = conn.preparedStatement(sql);
for
(
int
i=
1
;i<
100000
;i++){
ps.setInt(
1
, i);
ps.setString(
2
,
"name"
+i);
ps.setString(
3
,
"email"
+i);
ps.addBatch();
if
((i+
1
)%
1000
==
0
){
//批量处理
ps.executeBatch();
//清空ps中积攒的sql
ps.clearBatch();
}
}
|
注意:MySQL不支持批量处理。
批量处理应该设置一个上限,当批量处理列表中的sql累积到必定数量后,就应该执行,并在执行完成后,清空批量列表。
通常在excel导入数据的时候会用到批处理。
Java 经过JDBC得到链接之后,获得一个Connection 对象,能够从这个对象得到有关数据库管理系统的各类信息,包括数据库中的各个表,表中的各个列,数据类型,触发器,存储过程等各方面的信息。根据这些信息,JDBC能够访问一个实现事先并不了解的数据库。
获取这些信息的方法都是在DatabaseMetaData类的对象上实现的,而DataBaseMetaData对象是在Connection对象上得到的。
DatabaseMetaData 类中提供了许多方法用于得到数据源的各类信息,经过这些方法能够很是详细的了解数据库的信息:
getURL():返回一个String类对象,表明数据库的URL。
getUserName():返回链接当前数据库管理系统的用户名。
isReadOnly():返回一个boolean值,指示数据库是否只容许读操做。
getDatabaseProductName():返回数据库的产品名称。
getDatabaseProductVersion():返回数据库的版本号。
getDriverName():返回驱动驱动程序的名称。
getDriverVersion():返回驱动程序的版本号。
可用于获取关于 ResultSet 对象中列的类型和属性信息的对象:
getColumnName(int column):获取指定列的名称
getColumnCount():返回当前 ResultSet 对象中的列数。
getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
isNullable(int column):指示指定列中的值是否能够为 null。
isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。
1
|
Statement stmt = conn.createStatement(type,concurrency);
|
1
|
PreparedStatement stmt = conn.prepareStatement(sql,type,concurrency);
|
type说明:
ResultSet的Type | 说明 |
TYPE_FORWARD_ONLY | 结果集不能滚动,只可向前滚动 |
TYPE_SCROLL_INSENSITIVE | 双向滚动,但不及时更新,就是若是数据库里的数据修改过,并不在ResultSet中反应出来 |
TYPE_SCROLL_SENSITIVE | 双向滚动,并及时跟踪数据库的更新,以便更改ResultSet中的数据 |
Concurrency(并发类型)说明:
ResultSet的Concurrency(并发类型) | 说明 |
CONCUR_READ_ONLY | 结果集不可用于更新数据库 |
CONCUR_UPDATABLE | 结果集能够用于更新数据库 |
First:将指针移动到此 ResultSet 对象的第一行
Last:将指针移动到此 ResultSet 对象的最后一行
beforeFirst:将指针移动到此 ResultSet 对象的开头,正好位于第一行以前
afterLast:将指针移动到此 ResultSet 对象的末尾,正好位于最后一行以后
isFirst:检索指针是否位于此 ResultSet 对象的第一行
isLast:检索指针是否位于此 ResultSet 对象的最后一行
isBeforeFirst:检索指针是否位于此 ResultSet 对象的第一行以前
isAfterLast:检索指针是否位于此 ResultSet 对象的最后一行以后
Relative:按相对行数(或正或负)移动指针
Next:将指针从当前位置下移一行
Previous:将指针移动到此 ResultSet 对象的上一行
Absolute:将指针移动到此 ResultSet 对象的给定行编号
如:
rs.absolute(80); //将指针移动到ResultSet 对象的第80行记录。
注意:该特性对Oralce数据有效。可是在Mysql数据库中无效,Mysql只支持TYPE_SCROLL_INSENSITIVE,CONCUR_READ_ONLY。
普通的JDBC数据库链接使用 DriverManager 来获取,每次向数据库创建链接的时候都要将 Connection 加载到内存中,再验证用户名和密码。须要数据库链接的时候,就向数据库要求一个,执行完成后再断开链接。这样的方式将会消耗大量的资源和时间。数据库的链接资源并无获得很好的重复利用.若同时有几百人甚至几千人在线,频繁的进行数据库链接操做将占用不少的系统资源,严重的甚至会形成服务器的崩溃。
对于每一次数据库链接,使用完后都得断开。不然,若是程序出现异常而未能关闭,将会致使数据库系统中的内存泄漏,最终将致使重启数据库。
这种开发不能控制被建立的链接对象数,系统资源会被毫无顾及的分配出去,如链接过多,也可能致使内存泄漏,服务器崩溃。
为解决传统开发中的数据库链接问题,能够采用数据库链接池技术。
数据库链接池的基本思想就是为数据库链接创建一个“缓冲池”。预先在缓冲池中放入必定数量的链接,当须要创建数据库链接时,只需从“缓冲池”中取出一个,使用完毕以后再放回去。
数据库链接池负责分配、管理和释放数据库链接,它容许应用程序重复使用一个现有的数据库链接,而不是从新创建一个。
数据库链接池在初始化时将建立必定数量的数据库链接放到链接池中,这些数据库链接的数量是由最小数据库链接数来设定的。不管这些数据库链接是否被使用,链接池都将一直保证至少拥有这么多的链接数量。链接池的最大数据库链接数量限定了这个链接池能占有的最大链接数,当应用程序向链接池请求的链接数超过最大链接数量时,这些请求将被加入到等待队列中。
(1)资源重用:
因为数据库链接得以重用,避免了频繁建立,释放链接引发的大量性能开销。在减小系统消耗的基础上,另外一方面也增长了系统运行环境的平稳性。
(2)更快的系统反应速度
数据库链接池在初始化过程当中,每每已经建立了若干数据库链接置于链接池中备用。此时链接的初始化工做均已完成。对于业务请求处理而言,直接利用现有可用链接,避免了数据库链接初始化和释放过程的时间开销,从而减小了系统的响应时间。
(3)新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层经过数据库链接池的配置,实现某一应用最大可用数据库链接数的限制,避免某一应用独占全部的数据库资源。
(4)统一的链接管理,避免数据库链接泄露
在较为完善的数据库链接池实现中,可根据预先的占用超时设定,强制回收被占用链接,从而避免了常规数据库链接操做中可能出现的资源泄露。
JDBC 的数据库链接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口一般由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现,如:
DBCP 数据库链接池
C3P0 数据库链接池
Proxpool 数据库链接池
其中,DBCP和C3P0用得比较多。
Tomcat 在 7.0 之前的版本都是使用 commons-dbcp 作为链接池的实现。
数据源和数据库链接不一样,数据源无需建立多个,它是产生数据库链接的工厂,所以整个应用只须要一个数据源便可。
当数据库访问结束后,程序仍是像之前同样关闭数据库链接:conn.close(); 但它并无关闭数据库的物理链接,它仅仅把数据库链接释放,归还给了数据库链接池。
----------------------------------我是代码分割线---------------------------------------------
一个使用JDBC的小Demo
1 import java.sql.Connection; 2 import java.sql.DriverManager; 3 import java.sql.ResultSet; 4 import java.sql.SQLException; 5 import java.sql.Statement; 6 7 8 /** 9 * 使用JDBC链接数据库 10 * @author GXF 11 * 12 */ 13 public class TestJdbc { 14 15 public static void main(String[] args) { 16 TestJdbc testJdbc = new TestJdbc(); 17 testJdbc.testJdbc(); 18 } 19 20 /** 21 * 链接MySQL数据库 22 * test数据库 23 * usertable表 24 */ 25 public void testJdbc(){ 26 //加载驱动->经过url获得数据库链接对象->获取statement或者prestatment对象->执行SQL语句->处理结果集->关闭链接,释放资源 27 try { 28 String userName = "root"; 29 String passwd = ""; 30 String dbName = "test"; 31 //加载驱动 32 Class.forName("com.mysql.jdbc.Driver"); 33 //获取链接对象 34 String url = "jdbc:mysql://localhost:3306/" + dbName; 35 Connection con = DriverManager.getConnection(url, userName, passwd); 36 //建立statement 37 Statement st = con.createStatement(); 38 //执行SQL,查询表中全部数据 39 String sql = "select * from usertable"; 40 ResultSet rs = st.executeQuery(sql); 41 42 //处理结果集 43 while(rs.next()){ 44 System.out.println(rs.getString("username") + " " + rs.getString("passwd")); 45 }//while 46 47 //关闭链接,释放资源 48 rs.close(); 49 st.close(); 50 con.close(); 51 52 } catch (ClassNotFoundException e) { 53 54 e.printStackTrace(); 55 } catch (SQLException e) { 56 57 e.printStackTrace(); 58 } 59 } 60 61 }