12.JDBC&事务&连接池

JDBC
JDBC:java database connectivity SUN公司提供的一套操作数据库的标准规范,通过Java操作数据库
JDBC与数据库驱动的关系:接口与实现的关系
 

一、JDBC规范

掌握四个核心对象:
1、DriverManager:用于注册驱动
2、Connection: 表示与数据库创建的连接
3、Statement: 操作数据库sql语句的对象
4、ResultSet: 结果集或一张虚拟表
 
JDBC规范在JDK的java.sql.*、javax.sql.*中(接口)
数据库厂商提供的驱动:jar文件(实现类)
 

二、开发JDBC程序

1、创建数据库表,并向表中添加测试数据
2、创建java project项目,添加数据库驱动(*.jar)
3、实现JDBC操作
(1)加载驱动  
DriverManager. registerDriver(Driver driver) ;

Class.forName("com.mysql.jdbc.Driver");
(2)创建连接Connection
(3)得到执行sql语句的Statement对象
(4)执行sql语句,并返回结果
(5)处理结果
(6)关闭资源
 1 public void test1() throws Exception {
 2     // 加载驱动,反射
 3     Class.forName("com.mysql.jdbc.Driver");
 4     
 5     // 获取连接Connection,用java.sql.Connection sun的接口,多态,能用接口就不用类,导java.sql包
 6     Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1", "root", "GUOJIE123");
 7     
 8     // 得到执行sql语句的对象Statement,全是接口,实现在jar包中
 9     Statement createStatement = connection.createStatement();
10     
11     // 执行sql语句返回结果
12     ResultSet executeQuery = createStatement.executeQuery("select * from emp");
13     
14     // 处理结果
15     while(executeQuery.next()) {  // 开始指向表头,一次移动一行
16         System.out.println(executeQuery.getObject(1));  // 数据库都是从1开始,每次取一列
17         System.out.println(executeQuery.getObject(2)); 
18         System.out.println(executeQuery.getObject(3)); 
19     }
20     
21     // 关闭资源
22         executeQuery.close();
23         createStatement.close();
24         connection.close();
25     }
26 }

三、JDBC常用的类和接口

1、java.sql.Drivermanager类

创建连接

(1)注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());不建议使用
原因有2个:导致驱动被注册2次,强烈依赖数据库的驱动jar。
解决办法:Class.forName("com.mysql.jdbc.Driver");   // 反射加载,解耦,不需要编译路径
(2)与数据库建立连接
方式一:(常用)
getConnection(String url, String user, String password)  // 试图建立到给定数据库 URL 的连接
getConnection("jdbc:mysql://localhost:3306/day06", "root", "123");

URL:SUN公司与数据库厂商之间的一种协议。

jdbc:mysql://localhost:3306/db1
 协议 子协议     IP :   端口号 数据库
mysql:     jdbc:mysql://localhost:3306/day14 或者 jdbc:mysql:///day14(默认本机连接)
oracle:    jdbc:oracle:thin:@localhost:1521:sid   // thin瘦的,
方式二:
getConnection(String url, Properties info);
1 Properties info = new Properties();//要参考数据库文档
2 info.setProperty("user", "root");
3 info.setProperty("password","root");
方式三:
getConnection(String url);
DriverManager.getConnection("jdbc:mysql://localhost:3306/day14?user=root&password=root");

JUnit测试类

@Test注解方法,导包
import org.junit.Test;

注意:测试方法不能有返回值和参数

Assert.assertEquals();  // 断言
 

2、java.sql.Connection接口

一个连接

接口的实现在数据库驱动中,所有与数据库交互都是基于连接对象的。

Statement createStatement(); //创建操作sql语句的对象

3、java.sql.Statement接口

操作sql语句,并返回相应结果的对象(车)

接口的实现在数据库驱动中,用于执行静态 SQL 语句并返回它所生成结果的对象。
ResultSet executeQuery(String sql)   //根据查询语句返回结果集,只能执行select语句。
int executeUpdate(String sql)   // 根据执行的DML(insert update delete)语句,返回受影响的行数。
boolean execute(String sql)   // 此方法可以执行任意sql语句。返回boolean值,表示是否返回ResultSet结果集,仅当执行select语句,且有返回结果集时返回true, 其它语句都返回false
CRUD:客户端的增删改查,对应数据库的
数据库语句在数据库的图形界面里面写,SQL语句在客户端不用加;
 

4、java.sql.ResultSet接口

结果集(客户端存表数据的对象)
(1)封装结果集
提供一个游标,默认游标指向结果集第一行之前。
调用一次next(),游标向下移动一行。
提供一些get方法
 
封装数据的方法
Object getObject(int columnIndex); 根据序号取值,索引从1开始
Object getObject(String ColomnName); 根据列名取值。
 
将结果集中的数据封装到javaBean中
java的数据类型与数据库中的类型的关系
1 byte           tityint
2 short          smallint
3 int            int
4 long           bigint
5 float          float
6 double         double
7 String         char varchar
8 Date           date
Clob 数据库中大文本
 1 boolean next()   //  将光标从当前位置向下移动一行
 2 int getInt(int colIndex)  // 以int形式获取ResultSet结果集当前行指定列号值
 3 int getInt(String colLabel)   // 以int形式获取ResultSet结果集当前行指定列名值
 4 float getFloat(int colIndex)  //  以float形式获取ResultSet结果集当前行指定列号值
 5 float getFloat(String colLabel)   // 以float形式获取ResultSet结果集当前行指定列名值
 6 String getString(int colIndex)  //  以String 形式获取ResultSet结果集当前行指定列号值
 7 String getString(String colLabel)  //  以String形式获取ResultSet结果集当前行指定列名值
 8 Date getDate(int columnIndex);
 9 Date getDate(String columnName);
10 void close()   // 关闭ResultSet 对象

使用JDBC实现CRUD操作

 1 // 实体类,对应数据库的每一行
 2 public class Emp {
 3     private int id;
 4     private String name;
 5     private String gender;
 6     
 7     public int getId() {
 8         return id;
 9     }
10     public void setId(int id) {
11         this.id = id;
12     }
13     public String getName() {
14         return name;
15     }
16 
17     public void setName(String name) {
18         this.name = name;
19     }
20     public String getGender() {
21         return gender;
22     }
23     public void setGender(String gender) {
24         this.gender = gender;
25     }
26     
27     @Override
28     public String toString() {
29         return "Emp [id=" + id + ", name=" + name + ", gender=" + gender + "]";
30     }
31 }
 1 public class TestCRUD {
 2     
 3     @Test
 4     public void test() throws Exception {
 5         // 加载驱动,反射
 6         Class.forName("com.mysql.jdbc.Driver");
 7         
 8         // 获取连接Connection,用java.sql.Connection sun的接口,多态,能用接口就不用类,导java.sql包
 9         Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1", "root", "GUOJIE123");
10         
11         // 得到执行sql语句的对象Statement,全是接口,实现在jar包中
12         Statement createStatement = connection.createStatement();
13         
14         // 执行sql语句返回结果
15         ResultSet executeQuery = createStatement.executeQuery("select * from emp");
16         List<Emp> list = new ArrayList<Emp>();
17         
18         // 处理结果
19         while(executeQuery.next()) {  // 开始指向表头,一次移动一行
20             Emp emp = new Emp();  // 要是放在外面的话,会造成覆盖显示最后一行数据,因list中存放的是emp的引用,内存地址都一样,以最后修改的一次为结果
21             emp.setId(executeQuery.getInt("id"));
22             emp.setName(executeQuery.getString("name"));
23             emp.setGender(executeQuery.getString("gender"));
24             list.add(emp);
25         }
26         
27         // 关闭资源
28         executeQuery.close();
29         createStatement.close();
30         connection.close();
31         
32         for (Emp emp : list) {
33             System.out.println(emp);
34         }
35     }
36 }
5、释放资源
资源有限,要正确关闭
 
 1 @Test
 2 public void test2() throws Exception {
 3  // 获取连接Connection
 4 Connection connection = null;
 5 // 得到执行sql语句的对象Statement
 6 Statement createStatement = null;
 7 // 执行sql语句返回结果
 8 ResultSet executeQuery = null;
 9 try {
10     // 加载驱动,反射
11     Class.forName("com.mysql.jdbc.Driver");
12 
13     connection = DriverManager.getConnection(
14             "jdbc:mysql://localhost:3306/db1", "root", "GUOJIE123");
15 
16     createStatement = connection.createStatement();
17 
18     executeQuery = createStatement.executeQuery("select * from emp");
19 
20     // 处理结果
21     while (executeQuery.next()) { // 开始指向表头,一次移动一行
22         System.out.println(executeQuery.getObject(1)); // 数据库都是从1开始,每次取一列
23         System.out.println(executeQuery.getObject(2));
24         System.out.println(executeQuery.getObject(3));
25     }
26 } catch (Exception e) {
27     // TODO Auto-generated catch block
28     e.printStackTrace();
29 } finally {
30     // 关闭资源
31     if (executeQuery != null) {
32         try {  
33             executeQuery.close();  // executeQuery中还可能出错,继续try-catch
34         } catch (Exception e) {
35             e.printStackTrace();
36         }
37         executeQuery = null; // 让资源回收器回收
38     }
39 
40     if (createStatement != null) {
41         try {
42             createStatement.close();
43         } catch (Exception e) {
44             e.printStackTrace();
45         }
46         createStatement = null; // 让资源回收器回收
47     }
48 
49     if (connection != null) {
50         try {
51             connection.close();
52         } catch (Exception e) {
53             e.printStackTrace();
54         }
55         connection = null; // 让资源回收器回收
56         }
57     }
58 
59 }

SQL注入:

由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL 关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击

原理:

1.在输入时连接一个永远为真的一个值

2.使用mysql 中的 – 注释

解决SQL注入:使用PreparedStatement 取代 Statement,对sql语句进行预编译 

PreparedStatement pst=con.prepareStatement(String sql);    

PreparedStatement 解决SQL注入原理:运行在SQL中参数以?占位符的方式表示
select * from user where username = ? and password = ? ;
将带有?的SQL 发送给数据库完成编译 (不能执行的SQL 带有?的SQL 进行编译 叫做预编译),在SQL编译后发现缺少两个参数
PreparedStatement 可以将? 代替参数 发送给数据库服务器,因为SQL已经编译过,参数中特殊字符不会当做特殊字符编译,无法达到SQL注入的目的

四、事务

事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功

1、开启事务命令
1 start transaction   // 开启事务
2 Rollback //  回滚事务,将数据恢复到事务开始时状态
3 Commit  //  提交事务,对事务中进行操作,进行确认操作,事务在提交后,数据就不可恢复

2、mysql管理事务

方式一 :同时事务管理SQL 语句

同上开始事务命令

方式二:数据库中存在一个自动提交变量 ,通过 show variables like '%commit%'; ---- autocommint 值是 on,说明开启自动提交

关闭自动提交 set autocommit = off / set autocommit = 0

如果设置autocommit 为 off,意味着以后每条SQL 都会处于一个事务中,相当于每条SQL执行前 都执行 start transaction 

 注意:Oracle中 autocommit 默认就是 off
 
3、JDBC使用事务
当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可使用下列语句:
1 Connection.setAutoCommit(false); //  相当于start transaction
2 Connection.rollback();  rollback
3 Connection.commit();  commit

4、事物特性 ACID

(1)原子性(Atomicity)

原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
(2)一致性(Consistency)
事务前后数据的完整性必须保持一致。
(3)隔离性(Isolation)
事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
(4)持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

 
5、事务隔离级别
多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。

如果不考虑隔离性,可能会引发如下问题:

(1)脏读 :指一个事务读取另一个事务未提交的数据

(2)不可重复读:在一个事务先后两次读取发生数据不一致情况,第二次读取到另一个事务已经提交数据 (强调数据更新 update)

(3)虚读(幻读) :在一个事务中,第二次读取发生数据记录数的不同 ,读取到另一个事务已经提交数据 (强调数据记录变化 insert )

(4)丢失更新 :两个事务对同一条记录进行操作,后提交的事务,将先提交的事务的修改覆盖了

解决:数据库内部定义了四种隔离级别,用于解决三种隔离问题

1 Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)
2 Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)不可以避免虚读
3 Read committed:可避免脏读情况发生(读已提交)
4 Read uncommitted:最低级别,以上情况均无法保证。(读未提交)

mysql中默认的事务隔离级别是 Repeatable read.
oracle 中默认的事务隔离级别是 Read committed

操作数据库内部隔离级别
set session transaction isolation level 设置事务隔离级别
select @@tx_isolation 查询当前事务隔离级别

JDBC中指定事务的隔离级别 
Connection接口中定义事务隔离级别四个常量:
(1)static int TRANSACTION_READ_COMMITTED
指示不可以发生脏读的常量;不可重复读和虚读可以发生。
(2)static int TRANSACTION_READ_UNCOMMITTED
指示可以发生脏读 (dirty read)、不可重复读和虚读 (phantom read) 的常量。
(3)static int TRANSACTION_REPEATABLE_READ
指示不可以发生脏读和不可重复读的常量;虚读可以发生。
(4)static int TRANSACTION_SERIALIZABLE
指示不可以发生脏读、不可重复读和虚读的常量。

通过 void setTransactionIsolation(int level) 设置数据库隔离级别

6、事务隔离级别总结
read uncommitted 什么问题也解决不了.
read committed 可以解决脏读,其它解决不了.
Repeatable read 可以解决脏读,可以解决不可重复读,不能解决虚读.
Serializable 它会锁表,可以解决所有问题.

安全性:serializable > repeatable read > read committed > read uncommitted
性能 :serializable < repeatable read < read committed < read uncommitted

结论: 实际开发中,通常不会选择 serializable 和 read uncommitted 
mysql默认隔离级别 repeatable read ,oracle默认隔离级别 read committed

7、TheadLocal
Service层和Dao层保证共用一个Connection,通过方法传参的方式实现
要是实现接口时无参,用TheadLocal实现保证共用一个Connection

ThreadLocal可以理解成是一个Map集合
Map<Thread,Object>
set方法是向ThreadLocal中存储数据,那么当前的key值就是当前线程对象.
get方法是从ThreadLocal中获取数据,它是根据当前线程对象来获取值。

如果我们是在同一个线程中,只要在任意的一个位置存储了数据,在其它位置上,就可以获取到这个数据

关于JdbcUtils中使用ThreadLocal
1.声明一个ThreadLocal

private static final ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

2.在getConnection()方法中操作

1 Connection con = tl.get(); 直接从ThreadLocal中获取,第一次返回的是null.
2 if (con == null) {
3   // 2.获取连接
4   con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
5   tl.set(con); //将con装入到ThreadLocal中。
6 }
8、丢失更新

多个事务对同一条记录进行了操作,后提交的事务将先提交的事务操作覆盖了

解决丢失更新可以采用两种方式:

1.悲观锁
悲观锁 (假设丢失更新一定会发生 )

利用数据库内部锁机制,管理事务
提供的锁机制
(1)共享锁

select * from table lock in share mode(读锁、共享锁)

(2)排它锁

select * from table for update (写锁、排它锁)

update语句默认添加排它锁

2.乐观锁
乐观锁 (假设丢失更新不会发生)

采用程序中添加版本字段解决丢失更新问题

create table product (
id int,
name varchar(20),
updatetime timestamp
);

insert into product values(1,'冰箱',null);
update product set name='洗衣机' where id = 1;

解决丢失更新:在数据表添加版本字段,每次修改过记录后,版本字段都会更新,如果读取是版本字段id,
与修改时版本字段不一致,说明别人进行修改过数据 (重改)

五、连接池

就是创建一个容器,用于装入多个Connection对象,在使用连接对象时,从容器中获取一个Connection,
使用完成后,在将这个Connection重新装入到容器中。这个容器就是连接池。(DataSource),也叫做数据源.

我们可以通过连接池获取连接对象.
优点:节省创建连接与释放连接 性能消耗(连接池中连接起到复用的作用 ,提高程序性能)

1、自定义连接池
(1)创建一个MyDataSource类,在这个类中创建一个LinkedList<Connection>
(2)在其构造方法中初始化List集合,并向其中装入5个Connection对象。
(3)创建一个public Connection getConnection();从List集合中获取一个连接对象返回.
(4)创建一个 public void readd(Connection) 这个方法是将使用完成后的Connection对象重新装入到List集合中.

代码问题:
1.连接池的创建是有标准的.
在javax.sql包下定义了一个接口 DataSource
简单说,所有的连接池必须实现javax.sql.DataSource接口,我们的自定义连接池必须实现DataSource接口。

2.我们操作时,要使用标准,怎样可以让 con.close()它不是销毁,而是将其重新装入到连接池.
要解决这个问题,其本质就是将Connection中的close()方法的行为改变。
怎样可以改变一个方法的行为(对方法功能进行增强)
1.继承
2.装饰模式
1.装饰类与被装饰类要实现同一个接口或继承同一个父类
2.在装饰类中持有一个被装饰类引用
3.对方法进行功能增强。
3.动态代理
可以对行为增强
Proxy.newProxyInstance(ClassLoacer ,Class[],InvocationHandler);

结论:

Connection对象如果是从连接池中获取到的,那么它的close方法的行为已经改变了,不在是销毁,而是重新装入到连接池。

1.连接池必须实现javax.sql.DataSource接口。
2.要通过连接池获取连接对象 DataSource接口中有一个 getConnection方法.
3.将Connection重新装入到连接池 使用Connection的close()方法。

2、开源连接池
(1)DBCP(了解)

DBCP是apache的一个开源连接池。

要想使用DBCP连接池,要下载jar包
导入时要导入两个
commons-dbcp-1.4.jar
commons-pool-1.5.6.jar

关于dbcp连接池使用
1.手动配置(手动编码)

BasicDataSource bds = new BasicDataSource();

// 需要设置连接数据库最基本四个条件
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql:///day18");
bds.setUsername("root");
bds.setPassword("abc");

// 得到一个Connection
Connection con = bds.getConnection();

2.自动配置(使用配置文件)

1 Properties props = new Properties();
2 FileInputStream fis = new FileInputStream("D:\java1110\workspace\day18_2\src\dbcp.properties");
3 props.load(fis);
4 
5 DataSource ds = BasicDataSourceFactory.createDataSource(props);

(2)C3P0(必会)
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。
目前使用它的开源项目有Hibernate,Spring等。
c3p0与dbcp区别:

c3p0有自动回收空闲连接功能,DBCP没有

c3p0连接池使用
1.导包
c3p0-0.9.1.2.jar

使用
1.手动

1 ComboPooledDataSource cpds = new ComboPooledDataSource();
2 cpds.setDriverClass("com.mysql.jdbc.Driver");
3 cpds.setJdbcUrl("jdbc:mysql:///day18");
4 cpds.setUser("root");
5 cpds.setPassword("abc");

2.自动(使用配置文件)
c3p0的配置文件可以是properties也可以是xml.
c3p0的配置文件如果名称叫做 c3p0.properties or c3p0-config.xml 并且放置在classpath路径下(对于web应用就是classes目录)
那么c3p0会自动查找。

注意:我们其时只需要将配置文件放置在src下就可以。

使用:

ComboPooledDataSource cpds = new ComboPooledDataSource(); // 它会在指定的目录下查找指定名称的配置文件,并将其中内容加载。

(3)Tomcat内置连接池

Tomcat管理连接池

 要想将一个dbcp连接池让 tomcat管理,只需要创建一个context.xml配置文件,在配置文件中
<Context>
    <Resource name="jdbc/EmployeeDB" auth="Container"
                    type="javax.sql.DataSource" username="root" password="abc"
                    driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql:///day18"
                    maxActive="8" maxIdle="4"/>
</Context>

context.xml文件位置

1.在Tomcat/conf/context.xml 这时这个连接池是给整个服务器使用的。
2.在Tomcat/conf/Catalina/localhost 这时这个连接池只给localhost虚拟主机使用。
3.将context.xml文件放置在web应用的META-INF下,只给本项目使用
注意:如果是全局设置,那么我们需要将数据库驱动放置在tomcat/lib目录下

从tomcat中获取连接池

我们在servlet中获取连接池对象,用JNDI技术实现

1 Context context = new InitialContext();
2 Context envCtx = (Context)context.lookup("java:comp/env");  // 固定路径
3 DataSource datasource = (DataSource) envCtx.lookup("jdbc/EmployeeDB");

JNDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,
JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI SPI的实现,由管理者将JNDI API映射为特定的命名服务
和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。目录服务是一种命名服务,
在这种服务里,对象不但有名称,还有属性。

 

六、元数据

元数据(metaData)  指数据库中库、表、列的定义信息 
1.DataBaseMetaData 数据库元数据(了解
(1)通过DataBaseMetaData获得 数据库连接的基本参数
获取DataBaseMetaData:Connection接口中定义了一个方法 getMetaData();
1 getURL()   // 返回一个String类对象,代表数据库的URL。
2 getUserName()  // 返回连接当前数据库管理系统的用户名。
3 getDriverName()   // 返回驱动驱动程序的名称。
4 getPrimaryKeys(String catalog, String schema, String table)  // 返回指定表主键的相关描述    结果集

每个主键列描述都有以下列:
TABLE_CAT String => 表类别(可为 null)
TABLE_SCHEM String => 表模式(可为 null)
TABLE_NAME String => 表名称
COLUMN_NAME String => 列名称
KEY_SEQ short => 主键中的序列号(值 1 表示主键中的第一列,值 2 表示主键中的第二列)。
PK_NAME String => 主键的名称(可为 null)

(2)获得数据库、表、列、主键、外键 定义信息
1 getTables
2 getColumns
3 getPrimaryKeys
 
2、ParameterMetaData 参数元数据
参数元数据主要用于获取:sql语句中占位符的相关信息.
PreparedStatement . getParameterMetaData()  // 获得代表PreparedStatement元数据的ParameterMetaData对象
Select * from user where name=? And password=?

ParameterMetaData对象

1 getParameterCount()  // 获得指定参数的个数
2 getParameterTypeName(int param)   // 获得指定参数的sql类型

在获取参数类型时会产生异常
java.sql.SQLException: Parameter metadata not available for the given statement

解决方案:
在url后添加参数    jdbc:mysql:///day18?generateSimpleParameterMetadata=true
添加这个参数后,我们在获取,它的结果也是varchar,

原因:是mysql驱动的支持问题,Oracle可以

3、ResultSetMetaData 结果集元数据(重点)
ResultSet. getMetaData()   // 获得代表ResultSet对象元数据的ResultSetMetaData对象

ResultSetMetaData对象

1 getColumnCount()   // 返回resultset对象的列数
2 getColumnName(int column)   // 获得指定列的名称
3 getColumnTypeName(int column)  // 获得指定列的类型 

七、DbUtils

commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。因此dbutils成为很多不喜欢hibernate的公司的首选
 
1、DbUtils核心

1.QueryRunner
它是用于执行sql语句的类。
(1)query 用于执行select
(2)update 用于执行update delete insert
(3)batch 批处理
2.ResultSetHandler接口
用于定义结果集的封装
它提供九个实现类,可以进行不同的封装。
3.DbUtils
它提供关于关闭资源以及事务rollback,commit操作

2、QueryRunner

单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

(1)获取QueryRunner

new QueryRunner()   // 如果是使用这种构造创建的QueryRunner,它的事务是手动控制.
new QueryRunner(DataSource ds);  // 如果是使用这种构造,它的事务是自动事务,简单说,一条sql一个事务。

(2)QueryRunner中的三个核心方法

query
update
batch
对于上述三个方法,它们提供很多重载。
如果QueryRunner在创建时,没有传递DataSource参数,那么在使用
query,update,batch方法时,要传递Connection参数
如果QueryRunner在创建时,传递了Dataource参数,好么在使用
query,update,batch方法时,不需要传递Connection参数。

总结:
1 QueryRunner runner=new QueryRunner();
2 runner.query(Connection,sql,ResultSetHandler,Object... param);
3 runner.update(Connection,sql,Object...param);
4 runner.batch(Connection con,sql,Object[][] objs);
5                     
6 QueryRunner runner=new QueryRunner(DataSource ds);
7 runner.query(sql,ResultSetHandler,Object... param);
8 runner.update(sql,Object...param);
9 runner.batch(sql,Object[][] objs);

3、DbUtils类

提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的

public static void close(…) throws java.sql.SQLException   // DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。
public static void closeQuietly(…)  // 这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLException。
public static void commitAndCloseQuietly(Connection conn)   // 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。 
public static boolean loadDriver(java.lang.String driverClassName)  //这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。

模仿QueryRunner

 1 1.query方法模仿
 2 public <T> T query(Connection con, String sql, MyResultSetHandler<T> mrs,Object... params) throws SQLException {
 3 
 4         PreparedStatement pst = con.prepareStatement(sql); // 得到一个预处理的Statement.
 5         // 问题:sql语句中可能存在参数,需要对参数赋值。
 6 
 7         ParameterMetaData pmd = pst.getParameterMetaData();
 8         // 可以得到有几个参数
 9         int count = pmd.getParameterCount();
10         for (int i = 1; i <= count; i++) {
11             pst.setObject(i, params[i - 1]);
12         }
13 
14         ResultSet rs = pst.executeQuery(); // 得到了结果集,要将结果集封装成用户想要的对象,但是,工具不可能知道用户需求。
15 
16         return mrs.handle(rs);
17     }

18 2.update方法模仿
19 public int update(Connection con, String sql, Object... params) throws SQLException {
20 
21     PreparedStatement pst = con.prepareStatement(sql); // 得到一个预处理的Statement.
22     // 问题:sql语句中可能存在参数,需要对参数赋值。
23 
24     ParameterMetaData pmd = pst.getParameterMetaData();
25     // 可以得到有几个参数
26     int count = pmd.getParameterCount();
27     for (int i = 1; i <= count; i++) {
28         pst.setObject(i, params[i - 1]);
29     }
30 
31     int row = pst.executeUpdate();
32     // 关闭资源
33     pst.close();
34     return row;
35 }

4、ResultSetHandler接口

用于封装结果集
ResulsetHandler九个实现类(完成常规操作,而不需要自定义结果集封装)
(1)ArrayHandler:将结果集中第一条记录封装到Object[],数组中的每一个元素就是记录中的字段值。
(2)ArrayListHandler:将结果集中每一条记录封装到Object[],数组中的每一个元素就是记录中的字段值。再将这些数组装入到List集合。
(3)BeanHandler(重点):将结果集中第一条记录封装到一个javaBean中。
(4)BeanListHandler(重点):将结果集中每一条记录封装到javaBean中,再将javaBean封装到List集合.
(5)ColumnListHandler:将结果集中指定列的值封装到List集合.
(6)MapHandler: 将结果集中第一条记录封装到Map集合中,集合的 key就是字段名称,value就是字段值
(7)MapListHandler: 将结果集中每一条记录封装到Map集合中,集合的 key就是字段名称,value就是字段值,再将这些Map封装到List集合
(8)KeyedHandler:在使用指定的列的值做为一个Map集合的key,值为每一条记录的Map集合封装
(9)ScalarHandler:进行单值查询 select count(*) from account;
 1 // 使用BeanUtils实现
 2 Object obj = null;
 3 
 4 Map<String, String[]> map = new HashMap<String, String[]>();
 5 
 6 ResultSetMetaData md = rs.getMetaData();
 7 int count = md.getColumnCount();
 8 
 9 if (rs.next()) {
10     try {
11         obj = clazz.newInstance();
12         for (int i = 1; i <= count; i++) {
13             map.put(md.getColumnName(i),
14                     new String[] { rs.getString(md.getColumnName(i)) });
15         }
16         BeanUtils.populate(obj, map);
17     } catch (InstantiationException e) {
18         e.printStackTrace();
19     } catch (IllegalAccessException e) {
20         e.printStackTrace();
21     } catch (InvocationTargetException e) {
22         e.printStackTrace();
23     }
24 
25 }
26 
27 return obj;

客户信息的CURD操作

web  表现层、service 业务层、dao 持久层、utils 工具包、domain 实体类(javaBean) 

 // 查询所有客户信息 
1
// 1.在success.jsp页面添加连接 2 <a href="${pageContext.request.contextPath}/findAll">查看所有客户信息</a> 3 // 2.在CustomerFindAllServlet中调用service,在service中调用dao,最后得到一个List<Customer> 4 // 3.在showCustomer.jsp页面展示客户信息 5 <c:forEach items="${cs}" var="c"> 6 <tr> 7 <td><input type="checkbox"> 8 </td> 9 <td>${c.id }</td> 10 <td>${c.name}</td> 11 <td>${c.gender }</td> 12 <td>${c.birthday }</td> 13 <td>${c.cellphone }</td> 14 <td>${c.email }</td> 15 <td>${c.preference }</td> 16 <td>${c.type }</td> 17 <td>${c.description }</td> 18 <td><a>编辑</a>&nbsp;&nbsp;&nbsp;<a>删除</a></td> 19 </tr> 20 </c:forEach>
1 // 删除操作
2 // 1.在showCustomer.jsp页面的删除连接上添加参数  客户的id               
   <a href="${pageContext.request.contextPath}/delByid?id=${c.id}">删除</a>
3 // 2.创建一个CustomerDelByIdServlet,获取请求参数,调用service中删除方法.

删除完成后,需要重新跳转到查询所有的servlet中,再重新查询数据

response.sendRedirect(request.getContextPath()+"/findAll");
 1 // 编辑
 2 // 1.查询,做回显示
 3 <a href="${pageContext.request.contextPath}/findById?id=${c.id}">编辑</a>
 4 // 1.创建CustomerFindByIdServlet,得到要查询的id,调用service,得到Custonmer对象。
 5 // 2.将customer对象存储到request域,请求转发到customerInfo.jsp页面。
 6 // 3.在customerInfo.jsp页面展示客户信息
 7 // 注意:客户的id不能修改,所以使用<input type="hidden">
 8 
 9 // 2.修改
10 // 1.注意使用BeanUtils时的类型转换问题
11 // 2.注意编码问题    
12 // post:request.setCharacterEncoding("utf-8");
13 // get:手动转换  new String(request.getParameter(name).getBytes("iso8859-1"),"utf-8");
14 
15 // 3.进行修改操作
16 String sql = "update customer set name=?,gender=?,birthday=?,cellphone=?,email=?,preference=?,type=?,description=? where id=?";
17 // 修改完成后,在重新查询一次
18 response.sendRedirect(request.getContextPath() + "/findAll");

解决关于回显示时的问题

性别 应该使用radio

 1 // 使用自定义标签
 2 // 1.定义标签类   extends SimpleTagSupport
 3 // 2.定义tld文件
 4     <tag>
 5         <name>sex</name><!-- 标签名称 -->
 6         <tag-class>cn.itcast.customer.tag.GenderTag</tag-class><!-- 标签类 -->
 7         <body-content>empty</body-content><!-- 标签体中内容 -->
 8 
 9         <attribute>
10             <name>gender</name> <!-- 属性名称 -->
11             <required>true</required> <!-- 属性必须有 -->
12             <rtexprvalue>true</rtexprvalue><!-- 属性值可以接收el表达式 -->
13         </attribute>
14     </tag>
15 // 3.在页面上使用
16     // 1.使用taglib导入
17     // 2.使用
18         <my:sex gender="${c.gender}" />

使用虚拟主机可以将项目部署成顶级域名

1.在service.xml文件

1.端口修改为80

2. 配置主机

1  <Host name="www.customer.com"  appBase="D:java1110workspaceday19_2"
2                     unpackWARs="true" autoDeploy="true">  
3         <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
4          prefix="localhost_access_log." suffix=".txt"
5         pattern="%h %l %u %t &quot;%r&quot; %s %b" />
6         <Context path="" docBase="D:java1110workspaceday19_2WebRoot" />
7 </Host>

3.在hosts文件中配置

 127.0.0.1  www.customer.com
 
 
 
 
 
有志者,事竟成,破釜沉舟,百二秦关终属楚;苦心人,天不负,卧薪尝胆,三千越甲可吞吴。
原文地址:https://www.cnblogs.com/1989guojie/p/6127766.html