JDBC
通过Java操作数据库。
接口与实现的关系
一、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)注册驱动
不建议使用
原因有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测试类
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
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 (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集合。
重点):将结果集中第一条记录封装到一个javaBean中。
重点):将结果集中每一条记录封装到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> <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:\java1110\workspace\day19_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 "%r" %s %b" />
6 <Context path="" docBase="D:\java1110\workspace\day19_2\WebRoot" />
7 </Host>
3.在hosts文件中配置
127.0.0.1 www.customer.com