1. JDBC概述
1)JDBC:java Data Bace Connectivity(java数据库连接)
2)JDBC是java提供的一套操作数据库的API,基本全是接口和少量类,是一套java操作数据库的标准,主要位于java.sql和javax.sql包中。
3)数据库驱动就是这些借口的实现,每个数据库都有自己的驱动,数据库驱动都是有数据库厂商提供的而不是java提供的。
4)JDBC在项目中的位置及作用(如图)
5)我们在项目中与数据库打交道时只要操作JDBC这些接口,所以JDBC也实现的夸数据库的功能,在更换数据库时,只要更换一下数据库驱动就可以了,我们的java代码是不用改变的。
6)JDBC是一个接口,不同数据库厂商都去实现这些接口就形成了不同的数据库驱动。与之类似的有Severlet也是java提供的一套接口和标准,不同服务器厂商去实现这些接口从而形成了不同的数据库产品。
2. JDBC简单例子
1)基本步骤
注册驱动(只做一次)
建立连接(Connection)
创建执行SQL的语句(Statement)
执行SQL语句
处理执行结果(ResultSet)
释放资源
publicstaticvoid test() throws SQLException{
//1.注册驱动
DriverManager.registerDriver(newcom.mysql.jdbc.Driver());
//2.建立连接
Connection conn =DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc","root","");
//3.创建语句
Statement st =conn.createStatement();
//4.执行SQL语句
ResultSet rs =st.executeQuery("select * fromuser");
//5.处理执行结果
while(rs.next()){
System.out.println(rs.getString("id") + "\t" +rs.getString("name"));
}
//6.释放资源
rs.close();
st.close();
conn.close();
}
3. 分析JDBC的程序编写步骤和原理
a) 注册驱动
几种方式:
DriverManager.registerDriver(newcom.mysql.jdbc.Driver());//方式1
System.setProperty("jdbc.drivers","com.mysql.jdbc.Driver");//方式2
Class.forName("com.mysql.jdbc.Driver");//方式3
注册驱动其实就是将数据库驱动包中的Driver实现类,new一个对象,并将这个对象放进DriverManager类中的一个静态列表中,这个静态列表可以装多个驱动,也就是说我们可以注册多个驱动。
b) 获取连接
String url = "jdbc:mysql://localhost:3306/jdbc";
String userName = "root";
String password = "";
Connection conn = DriverManager.getConnection(url,userName,password);
url为数据库连接字符串,每个数据库的连接字符串个格式略有不同
获取连接是用DriverManage的getConnection()方法,这个方法是循环那个驱动列表挨个问每个驱动,根据这个url能否建立一个连接,只要有一个能创建连接,就创建并返回一个连接。
4. 规范和风封装JDBC代码
将注册驱动,获取连接,释放数据库资源抽取出放在一个工具类中
publicfinalclass JDBCUtils {
// 私有化构造方法
private JDBCUtils() {
}
privatestatic String url = "jdbc:mysql://localhost:3306/jdbc";
privatestatic String userName = "root";
privatestatic String password = "";
//静态代码块来注册驱动,保证驱动只注册一次
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取数据库连接
publicstatic Connection getConnection() throws Exception{
return DriverManager.getConnection(url,userName,password);
}
//释放资源
publicstaticvoid free(ResultSet rs,Statement st,Connection conn){
try {
if(rs != null)
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}finally{
try {
if(st != null)
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}finally{
if(conn != null)
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
5.JDBCUtils用单例实现问题
6.JDBC对数据库的CRUD
就是insert,update,select ,delete语句,select语句用Statement的excuteQuery()方法返回ResultSet,insert,update,delete语句用用Statement的excuteUpdate()方法返回有影响的行数
7.Stament有SQL注入的问题
publicstaticvoid template(String name) throws Exception{
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try{
//2.获取连接
conn = JDBCUtils.getConnection();
//3.创建语句
st =conn.createStatement();
//4.执行SQL
rs =st.executeQuery("select * from userwhere name ='"+name+"'");
//5.处理结果集
while(rs.next()){
System.out.println(rs.getString("id")+"\t" +rs.getString("name")+"\t"+ rs.getString("birthday")+"\t"+rs.getFloat("money"));
}
}finally{
JDBCUtils.free(rs, st, conn);
}
}
用Statement执行Sql语句时有SQL注入的问题,上面的方法如果调用时传个参数为”’ or 1 or ’”,就会将所有的记录都查出来
8.PreparedStatement可以解决Sql注入的问题
/**
* preparedStatement实例
*/
publicstaticvoidpreparedStatementTemplate(String name) throws Exception{
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
//2.获取连接
conn = JDBCUtils.getConnection();
//3.创建语句
ps =conn.prepareStatement("select *from user where name =?");
//4.执行SQL
ps.setString(1,name);
rs = ps.executeQuery();
//5.处理结果集
while(rs.next()){
System.out.println(rs.getString("id")+"\t" + rs.getString("name")+"\t"+ rs.getString("birthday")+"\t"+rs.getFloat("money"));
}
}finally{
JDBCUtils.free(rs, ps, conn);
}
}
注意:PreparedStatement执行Sql语句时要注意,要调用不带参数的executeQuery()和executeUpdtae()方法,而不是带参数的,掉带参数的相当于仍然是调的Statement的方法,因为PreparedStatement是继承自Statement的。
9.eclipse中导入的JAR包可以关联源代码
10.JDBC的数据类型和日期问题
JDBC中的数据类型主要用在两个地方:
1) PreparedStatement的setXXX()方法
2) ResultSet的getXXX()方法
大部分类型很容易理解,只有Date类型有点特殊,JDBC中的Date是java.sql包中的Date,是从java.util包中的Date继承来的。Util包中的date是带时分秒的,而java.sql包中的Date是只有日期的,java.sql包中有Date,Time,Timestamp与数据库中日期的的三种类型是一一对应的,他们三个都是继承自util包中的Date类。
11.数据大文本字段
数据库Varchar类型的字段最长只能存储255个字符,如果字符长度超过255,就要用大文本数据类型clob,不同数据库clob类型的名称不一样,mySql中交Text,有的直接叫clob。
具体用到PreparedStament的setsetCharacterStream()方法和ResultSet的getCharacterStream()和getClob()方法。其实数据库中是clob类型时,在PreparedStatement的setString()方法和ResultSet的getgetString()照样可以用。
12. 大的二进制数据类型
数据库自存储二进制数据时用 blob数据类型。PreparedStatement的setBinaryStream()的方法,ResultSet的getBlob()方法和getBinaryStream()方法。
13.JDBC的其他数据类型
14.解答学员疑问
15.实际项目总如何运用的JDBC
JDBC查询结果为ResultSet,Dao层中不能讲ResultSet直接返回给业务逻辑层,因为业务逻辑层拿到ResultSet时,ResultSet已经关闭了。所以Dao层查询出的结果ResultSet,我们要封装成JAVA对象后再返回给业务逻辑层。
16.Dao层设计思想和搭建骨架
数据库每个表都要建一个diMain对象以之相对应,从数据库得到的结果是ResultSet,我们要将ResultSet装换成doMain对象。
17.结合service层讲解Dao层的异常处理
1)Dao层的异常,一般不处理,直接往上层抛,也就是抛给ServiceC层。
2)Dao层中抛出的异常如果是一种特定的异常如SQLException,那Service方法中调Dao层时,就要处理这些异常,要么catch要么在往上抛,但这样的话Service的代码就写死了,Dao层就不能换别的实现了,别的实现回抛别的异常,那Service的方法还的改,所以在到层我们最好统一抛出一个自定义异常(DaoException),这个异常要继承在RunTimeException。在Service方法中统一处理这一种异常。这样Dao层换实现也不会影响Service 的代码。一定要是RunTimeException因为种异常不要求程序中一定处理,所以不用catch,也不用往上抛,Dao的方法后面就不用必须throws了。方法显得干净好多,也给了Service一个选择,让Service层不是一定要处理Dao层的异常,因为Dao抛出的是运行时异常。这样既达到将异常通知上一层的目的,又让我们的接口不受污染(方法后带有特定的异常)。
18.完成整个DAO层的实现代码
19.JDBC使用DAO工厂模式
1)Service层使用Dao时,要先这么写:
XXXDao xxxDao = new xxxDaoIml();
然后再用xxxDao的方法,这样的话,Service层的代码就有写死了,如果Dao层的实现改了,例如改为xxxDaoxxxImpl,那Service层的代码必须又要改了,改成:
XXXDao xxxDao = new xxxDaoxxxImpl();其实用Spring的话就接解决了,因为Spring用的是注入方式。但没有Spring的话,我们用Dao的工厂类实现。实现后Service中就改成这样了,XXXDao xxxDao = XXXDaoFactory.getXXXDao();这样即使Dao的实现换了,我们Service层的代码也不用换了。
DaoFactor用来生产Dao的,至于生产那个Dao,就用Properties文件中配置的来决定。有一个取Properties文件路径的方法,用一个简单方法是,取到类加载器然后用类加载器取,这样就不用写路径了,因为所有文件都被加载到了虚拟机了,只要文件位于ClassPath下就能取到,如下下:
InputStream stream =JDBCTest.class.getClassLoader().getResourceAsStream("aaaa.properties");
20.事务的概念与JDBC事务处理
1)JDBC事务过程如下:
conn.setAutoCommit(false);
conn.commit();
conn.rollback();
21.事务的保存点处理
事务可以回滚到某个点。
SavePoint sp = conn.setSavePoint();
Conn.rollback(sp)
22.JTA分布式事务简单介绍
23.事务的隔离级别
一般有四种:
1)读未提交
2)读已提交
3)可重复读
4)可串行化
但不同数据库所支持的隔离级别不同,而且不同数据库的默认事务隔离级别也不同。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(Read uncommitted) | V | V | V |
读已提交(Read committed) | x | V | V |
可重复读(Repeatable read) | x | x | V |
可串行化(Serializable ) | x | x | x |
这样为连接设置事务隔离级别:
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
24.JDBC调用存储过程
JDBC调用存储过程的例子:
CallableStatement(从PreperedStatement扩展来)
cs = connection.prepareCall(“{callpsname(?,?,?)}”);
cs.registerOutParameter(index,Types.INTEGER);
cs.setXXX(i, xxxx);
cs.executeUpdate();
int id=cs.getInt(index);
25,JDBC批处理功能
PreparedStatement.addBatch();
PreparedStatement.executeBatch();
26.可滚动结果集与分页技术
1)可滚动的结果集是指,ResultSet不只是能往后滚(rs.next()),而且还可以往前滚(rs.previous()),还可以指定滚到哪一行;
l 可滚动的结果集
Statement st =
connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet rs = st.executeQuery(sql);
rs.beforeFirst(); rs.afterLast();rs.first();rs.isFirst();rs.last();rs.isLast();
rs.absolute(9);rs.moveToInsertRow();
27.可更新和对更新敏感的结果集
查出结果集后,更新结果集,也会造成数据库的修改。
l 可更新的结果集
conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
rs.updateString("col name", "new value");
rs.updateRow();
28.数据库的元数据信息
通过数据库连接可以获取数据库的一些信息(数据库元数据)
DatabaseMetaData meta =connection.getMetaData();
DatabaseMetaData中有很多方法,可以获取数据可得各种信息
29.参数的元数据信息
可以获取PreparedStatement的参数的原信息
l ParameterMetaData pmd = preparedStatement.getParameterMetaData();
l 通过 ParameterMetaData可以获得参数信息。
通过这些特性可以写出比较灵活的JDBC操作的方法。
30.利用结果集元数据将查询结果封装为map
l ResultSetMetaData meta = rs.getMetaData();
l 通过ResultSetMetaData可以获得结果有几列、各列名、各列别名、各列类型等。
30.利用结果集元数据将查询结果封装为map
l 用反射ResultSetMetaData将查询结果读入对象中(简单的O/RMapping)
1)让SQL语句中列别名和要读入的对象属性名一样;
2)通过ResultSetMetaData获得结果列数和列别名;
3)通过反射将对象的所有setXxx方法找到;
4)将3)找到的方法setXxx和2)找到的列别名进行匹配(即方法中的xxx于列别名相等);
5)由上一步找到的方法和列别名对应关系进行赋值
Method.invoke(obj,rs.getObject(columnAliasName));
31.Java反射技术入门
32.Java反射的更多细节
33.利用Java反射技术将查询结果封装为对象
34.编写一个基本的连接池来实现连接的复用
l DataSource用来取代DriverManager来获取Connection;
l 通过DataSource获得Connection速度很快;
l 通过DataSource获得的Connection都是已经被包裹过的(不是驱动原来的连接),他的close方法已经被修改。
l 一般DataSource内部会用一个连接池来缓存Connection,这样可以大幅度提高数据库的访问速度;
l 连接池可以理解成一个能够存放Connection的Collection;
l 我们的程序只和DataSource打交道,不会直接访问连接池;
35.对基本连接池进行一些工程细节上的优化
36.通过代理模式来保持用户关闭连接的习惯
37.Java的动态代理及使用该技术完善连接代理
38.标准DataSource接口及数据源的总结介绍
39.如何使用开源项目DBCP(实际项目中常用)
40.将DAO中的修改方法提取到抽象父类中
41.使用模板方法设计模式处理DAO中的查询方法
42.使用策略模式对模板方法设计模式进行改进
.
.
.
.
48