四JDBC学习笔记

 

 

 

 

 

 

 

 

 

 

 

 

JDBC学习笔记

 

Java相关课程系列笔记之四

 

 

 

 

 

 

 

 

 

 

 

笔记内容说明

JDBC范传奇老师主讲,占笔记内容100%

 

 

一、 JDBC概述 1

1.1 什么是JDBC 1

1.2什么是驱动 1

1.3 SQL lite 1

1.4如何使用Java连接某种数据库 1

1.5连接数据库并操作 1

1.6连接数据库时常见的错误 1

二、 JDBC核心API 2

2.1 Connection 2

2.2 Statement 2

2.3 ResultSet 2

2.4 DriverManager 2

2.5 UUID 3

2.6案例:使用JDBC连接数据库,并操作SQL语句 3

2.7案例:通过JDBC创建表 4

2.8案例:使用JDBC向表中插入数据 4

2.9遍历Student_chang 5

三、 JDBC核心APIPreparedStatement 6

3.1 Statement的缺点 6

3.2 PreparedStatement的优点 6

3.3 PreparedStatement的常用方法 6

3.4案例详见第五章StudentDAO 6

四、 Connection封装 7

五、 DAO 8

5.1持久类封装 8

5.2 DAO 8

5.3 Properties 8

5.4案例:注册系统 8

六、 批处理 12

6.1批处理的优点 12

6.2 JDBC批处理API 12

6.3案例:详见8.4案例step7 12

七、 事务处理 13

7.1事务特性ACID 13

7.2 JDBC中对事务的支持(API 13

八、 DAO事务封装 14

8.1 ThreadLocal原理 14

8.2原理图 14

8.3 ThreadLocal核心API 14

8.4案例:登录系统(使用ThreadLocal实现连接共享) 14

九、 分页查询 17

9.1分页查询的基本原理 17

9.2为何使用分页查询 17

9.3 Oracle分页查询SQL语句 17

9.4 MySQL分页查询SQL语句 17

9.5“假”分页 17

9.6案例:分页查询 18

 

 

 

 

一、JDBC概述

1.1 什么是JDBC

1Java的设计者希望使用相同的方式访问不同的数据库。

2JDBCJava用于统一连接数据库并操作数据库的一组通用接口定义(即通过一系列接口定义了访问数据库的通用API)。

3JDBC是连接数据库的规范,不同的数据库厂商若想让Java语言可以对其操作,就需要实现一组类,这组类需要实现Java提供的这组用于连接数据库的接口,并实现其中定义的相关方法。那么不同的数据库厂商根据各自数据库的特点,去提供对JDBC的实现(实现类包),那么这组类就是该数据库的驱动包了。

4)原理图:

 

1.2什么是驱动

简单的说就是让软件知道如何去操作硬件。

1.3 SQL lite

是轻量级的数据库,常用于嵌入式。

1.4如何使用Java连接某种数据库

需要两个部分:1)使用JDBC连接数据库(导入某数据库的.jar包)。

  2)提供对该数据库的驱动包(使用静态方法Class.forName注册驱动)。

1.5连接数据库并操作

1)打开与数据库的连接(使用DriverManager.getConnection获取连接)。

2)执行SQL语句(使用Statement或者PreparedStatement)。

3)得到结果。

1.6连接数据库时常见的错误

1)报错ClassNotFoundException则有两种情况:

①驱动包没导入。

Class.forName()中的字符串拼写有误。

2)报错port number,应注意:

①连接数据库时输入数据库路径时没有添加端口号。

Oracle数据库的完整写法应为:jdbc:oracle:thin:@IP地址:端口号:数据库名

注意事项:Oracle数据库默认端口号1521MySql数据库默认端口号为3306

二、
JDBC核心API

2.1 Connection

接口,需导入java.sql.Connnection包,与特定数据库进行连接(会话)。

2.2 Statement

接口,需导入java.sql.Statement包,用于执行静态SQL语句并返回它所生成结果的对象。

1ResultSet executeQuery(String sql) throws SQLException方法:执行给定的SQL语句(通常为静态SQL SELECT语句),该语句返回单个ResultSet对象。

2boolean execute(String sql) throws SQLException方法:执行给定的SQL语句,该语句可能返回多个结果。如果第一个结果为ResultSet对象,则返回true;如果其为更新计数或者不存在任何结果,则返回false。详细介绍请看2.6案例注释。

3int executeUpdate(String sql) throws SQLException方法:执行给定SQL语句,该语句可能为INSERTUPDATE、DELETEDML语句),或者不返回任何内容的DDL语句。返回值:对于数据操作语句(DML语句),返回行计数对于DDL语句,返回0。

4boolean execute(String sql)方法:返回结果为truefalse,常用与执行表级操作的SQL语句,如建表、删表等,创建表若失败实际上是会直接抛出异常的。false:为建表成功的标志。

5exectue()方法:原则上可以执行任意SQL语句。返回true:若执行结果为一个结果集(ResultSet)。返回false:为其他信息(如影响表数据总条数等)。所以我们通常不会使用execute去执行查询语句。

6int executeUpdate(String sql) throws SQLException方法:返回值int,返回值为当前执行的SQL语句影响了数据库数据的总条数;该方法常用与执行insertupdatedelete语句。

7)在底层一定会用到网络Socket和流,但我们不用关心使用字符还是字节接收,都由Statement做了。

2.3 ResultSet

接口,表示数据库结果集的数据表(很像一个集合),通常通过执行查询数据库的语句生成。

1ResultSet特点:按行遍历,按字段取值。

2)它的next()方法包含了是否有下一条记录的hasnext()方法。

3)按字段取值时,getString(int)方法中的int,代表结果集的第几列,

注意事项:这里的int1开始,和Java对索引的习惯不同。

2.4 DriverManager

它是管理一组JDBC驱动程序的类。

1Connection getConnection(String url,String user,String password)方法:静态方法,建立与给定数据库URL的连接(DriverManager试图从已注册的JDBC驱动程序集中选择一个适当的驱动程序)。

2DriverManager如何知道某种数据库已注册的?

例如:oracle.jdbc.driver.OracleDriver类在Class.forName()的时候被载入JVM

OracleDriverJDBCDriver的子类,它被要求在静态初始化的时候要将自身驱动的信息通过DriverManager的静态方法注册进去,这样DriverManager就知道应该如何通过OracleDriver去连接该数据库了。所以之后就可以通过DrvierManager的另一个静态方法:getConnection()来根据之前注册的驱动信息获取连接了:

  Connection conn=DriverManager.getConnetion("","","");

2.5 UUID

UUID为通用唯一标识码(Universally Unique Indentifier)对于大数据量的表来说,UUID是存放ID最好的方式。

1Java提供的支持(用法详见2.8案例)

UUID类:UUID.randomUUID().toString():获得一个36位不重复的字符串。

2Oracle提供的支持(用法详见8.4案例step7

函数sys_guid():获取一个32位不重复的字符串。

2.6案例:使用JDBC连接数据库,并操作SQL语句

/** 连接数据库一定要捕获异常的 */

Connection conn=null;//定义在try外面是用于在finally块中关闭它,同时局部变量在使用前,一定要初始化!!

try{ /** 与数据库进行连接分为两步:1)注册驱动:不同的数据库实现不尽相同,所以要使用不同数据库厂商提供的驱动包。连接不同数据库,传入的字符串不尽相同,但是目的相同,都是注册驱动。而对于驱动包路径,名字是固定的,基本上不会变的!2)根据数据库的位置(路径)以及用户名和密码进行连接 */

Class.forName("oracle.jdbc.driver.OracleDriver");

/** 路径:不同数据库连接的路径写法不尽相同,Oracle的写法:                          jdbc:oracle:thin:@HOST:DB_NAME

       其中HOST包含两部分:IP地址和端口号;本机则使用localhost127.0.0.1 */

conn=DriverManager.getConnection("jdbc:oracle:thin:@192.168.0.20:1521:tarena",

"jsd1304","jsd1304");

/** 使用SQL语句来操作数据库,若想执行SQL语句,我们需要使用一个专门处理SQL语句的类,这个类叫做Statement */

Statement state=conn.createStatement();

/** user_tablesOracle用于存储当前用户创建的所有表的信息,其中一个字段叫做table_name用户保存的表名 */

String sql="SELECT table_name FROM user_tables";

/** 通过Statement执行查询语句,当查询完毕后,数据库会将查询结果返回,Statement会将查询结果存储到ResultSet*/

ResultSet rs=state.executeQuery(sql);

while(rs.next()){ //按行遍历,包含了是否有下一条记录的方法hasnext()

/** 按字段取值;整数参数:结果集的第几列。注意:这里从1开始,和Java对索引的习惯不同 */

String tableName=rs.getString(1);

System.out.println(tableName);

}

/** 底层一定会用到网络socket和流,但我们不用关心使用字符还是字节接收,都由Statement做了 */

rs.close(); state.close();

}catch(Exception e){ e.printStackTrace();

}finally{ if(conn!=null){   try {  conn.close();  } catch (SQLException e) {

         e.printStackTrace();   }   } }

注意事项: 养成良好的编码习惯:所有SQL关键字用纯大写,其他内容用纯小写 。

2.7案例:通过JDBC创建表

Connection conn=null;

try{ //1 注册驱动

Class.forName("oracle.jdbc.driver.OracleDriver");

//2 打开连接,支持import java.sql.*,但全导入较耗费性能

conn=DriverManager.getConnection(

"jdbc:oracle:thin:@192.168.0.20:1521:tarena","jsd1304","jsd1304");

//3 创建用于执行SQL语句的Statement

Statement state=conn.createStatement();

//创建建表语句

String sql="CREATE TABLE Student_chang(" +

"id varchar2(36) PRIMARY KEY," +"name varchar2(30)," +

"age number(2)," + "sex varchar2(2)" + ")";

//execute()方法详见2.2

if(!state.execute(sql)){ System.out.println("创建表成功!");

}else{ System.out.println("创建失败!"); }

state.close();

}catch (Exception e){ e.printStackTrace();

}finally{ if(conn!=null){   try {  conn.close();  } catch (SQLException e) {

         e.printStackTrace();   }   } }

2.8案例:使用JDBC向表中插入数据

Connection conn=null;

try{ Class.forName("oracle.jdbc.driver.OracleDriver");

conn=DriverManager.getConnection(

"jdbc:oracle:thin:@192.168.0.20:1521:tarena","jsd1304","jsd1304");

Statement state=conn.createStatement();

//UUID详见2.5

String uuid=UUID.randomUUID().toString(); System.out.println(uuid);

String sql="INSERT INTO Student_chang VALUES('"+uuid +"','Chang',22,'1')";

    //String sql="INSERT INTO Student_chang VALUES(sys_guid(),'chang',23,'1')";

//判断insert语句是否成功,看返回值是否大于0executeUpdate方法详见2.2

if(state.executeUpdate(sql)>0){ System.out.println("插入数据成功"); }

state.close();

}catch(Exception e){ e.printStackTrace();

}finally{ if(conn!=null){  try { conn.close(); } catch (SQLException e) {

    e.printStackTrace(); } } }

2.9遍历Student_chang

Connection conn=null;

try{ Class.forName("oracle.jdbc.driver.OracleDriver");

conn=DriverManager.getConnection(

"jdbc:oracle:thin:@192.168.0.20:1521:tarena","jsd1304","jsd1304");

Statement state=conn.createStatement();

String sql="SELECT * FROM Student_chang";

ResultSet rs=state.executeQuery(sql);

while(rs.next()){ String id=rs.getString(1);

String name=rs.getString("name");//不知第几列也可写列名

int age=rs.getInt("age");

String sex=rs.getString(4).equals("1")?"":"";

System.out.println(id+","+name+","+age+","+sex); }

rs.close(); state.close();

}catch(Exception e){ e.printStackTrace();

}finally{ if(conn!=null){  try {  conn.close(); } catch (SQLException e) {

 e.printStackTrace(); }  } }

三、
JDBC核心APIPreparedStatement

3.1 Statement的缺点

1)用Statement操作时代码的可读性和可维护性差,编写SQL语句复杂。

2Statement操作SQL语句,每执行一次都要对传入的语句编译一次,效率比较差。

3)不安全可能出现SQL注入攻击,详见9.6案例step3

4)扩展:XSS攻击、html代码注入攻击、struts2 OGNL存在可以远程执行底层操作系统命令的漏洞。

3.2 PreparedStatement的优点

1PreparedStatement实例包含已编译的SQL语句。包含于PreparedStatement对象中的SQL语句可具有一个或多个IN参数。IN参数的值在SQL语句创建时未被指定。该语句为每个IN参数保留一个问号(“?”)作为占位符,不考虑类型。每个问号的值必须在该语句执行之前,通过适当的setStringsetIntsetDouble……等方法来提供。

2)由于PreparedStatement对象已预编译过,所以其执行速度要快于Statement对象。因此,多次执行的SQL语句经常创建为PreparedStatement对象,以提高效率。

3PreparedStatement继承于Statement,其中三种方法:executeexecuteQueryexecuteUpdate都已被更改为不再需要参数了。因为我们在获取PreparedStatement时已经将SQL语句传入了。所以执行就可以,不需要再传入SQL

4PreparedStatement可以进行批量处理。

5)可以防止SQL注入攻击。

u 注意事项:

v 使用预编译语句,你传入的任何内容就不会和原来的语句发生任何匹配的关系,只要全使用预编译语句,你就不用对传入的数据作任何的过滤。

对一个表只作一个操作用PreparedStatement,效率高、方便

对表进行2种及以上的操作用Statement

3.3 PreparedStatement的常用方法

1boolean execute()可以是任何种类的SQL语句。一些预处理过的语句返回多个结果,execute 方法处理这些复杂的语句,executeQuery executeUpdate 处理形式更简单的语句。如果第一个结果是ResultSet对象,则返回true;如果第一个结果是更新计数或者没有结果,则返回false。 

2ResultSet executeQuery():在此PreparedStatement对象中执行SQL查询,并返回该查询生成的ResultSet对象。

3int executeUpdate():在此PreparedStatement对象中执行SQL语句,该语句必须是一个DML语句,比如INSERTUPDATEDELETE语句;或者是无返回内容的语句,比如DDL语句。

4void addBatch():将一组参数添加到此PreparedStatement对象的批处理命令中。其他批处理方法,见第六章。 

5void setObject(int parameterIndex, Object x):第一个参数用于设置“?”中的值,且从1开始!该方法可将任意给定的参数类型转换为所对应的SQL类型。类似setIntsetString等。

3.4案例详见第五章StudentDAO

四、
Connection封装

可建立一个DBUtils类,用于对数据库连接的封装。

    private static String driver; private static String url;

private static String user; private static String password;

static {//此处详细说明见5.4案例step2

try { Properties props = new Properties();//Properties类详见5.3

props.load(DBUtils.class.getClassLoader().getResourceAsStream(

"db.properties"));

driver = props.getProperty("driver");

url = props.getProperty("url");

user = props.getProperty("user");

password = props.getProperty("password");

Class.forName(driver);//注意这里没有引号!

} catch (IOException e) { e.printStackTrace();

} catch (ClassNotFoundException e) { e.printStackTrace(); }

}

/**封装连接操作,供其他类继承,再次使用数据库连接的时候,可以直接调用openConnection获取数据连接,其中urluserpwd通过上面的.properties文件加载 */

protected static Connection getConnection() throws SQLException{

return DriverManager.getConnection(url,user,pwd);

}

/** 封装关闭操作,供其他类继承,关闭连接时调用该方法 */

protected static void closeConnection(Connection conn){

if(conn!=null){

try{ conn.close(); } catch (SQLExcption e) {  }

}//catch块内容可不写,即所谓的“安静”的关闭连接

}

五、
DAO

5.1持久类封装

对象关系映射(ORM)使用描述对象和数据库之间映射的元数据,将Java程序中的对象自动持久化到关系数据库中。1)表和类对应。2)表中的字段和类的属性对应。3)记录和对象对应。

5.2 DAO

1DAO:数据连接对象(DataAccessObjects

2)作用:将数据库中的数据转化为Java的对象并返回(即读数据),将Java的对象转化为数据库中表的一条数据(即写数据)。

3Java对象在这里就是所谓的实体entityDAO要达到的目的:对数据库数据的操作面向对象化。

4)实体:用Java中的对象去描述数据库中的某表中的某一条记录。

比如:Student表有字段idnameagesex,则对应的Java类中有Student类,属性有idnameagesex

5)实体类:用于对应数据库中的表。通常实体类的名字和数据库中表的名字一致。

u 注意事项:实体类代表表,属性代表字段,对象代表一条数据。

5.3 Properties

用于读取.properties”文本文件的类,导入java.util.Properties包。

1)“.properties”文件是一个纯文本文件,里面定义的内容格式有要求,必须是key=value的形式,并且以行为单位。一行只记录一条数据!

2Properties类可以方便的读取properties文件,并将内容以类似HashMap的形式进行读取。

3db.properties文件里的内容如下:

  jdbc.driver=oracle.jdbc.driver.OracleDriver

  jdbc.url=jdbc:oracle:thin:@192.168.0.20:1521:tarena

  jdbc.user=jsd1304

  jdbc.pwd=jsd1304

u 注意事项:读取的都是字符串!不用写双引号,无空格!

4getProperty(String key)方法:该方法可以从properties文件中获取数据,如:jdbc.driver=oracle.jdbc.driver.OracleDriver。获取方式是将jdbc.driverkey作为参数调用方法。返回的就是等号右面的值oracle.jdbc.driver.OracleDriver了。

5.4案例:注册系统

step1Student实体类

public class Student implements Serializable{

private static final long seriaLversionUID=1L;

private String id; private String name; private int age; private String sex;

……各自的get/set方法 }

注意事项:该类描述数据库中Student表,其每一个实例都可以代表Student表的一行数据。通常情况下实体都是可以序列化的!

step2BaseDAO父类(基础类,提供所有DAO都需要具备的特性)

private static Properties properties=new Properties();

private static String driver="";//"oracle.jdbc.driver.OracleDriver";

private static String url="";//"jdbc:oracle:thin:@192.168.0.20:1521:tarena";

private static String user="";//"jsd1304";

private static String pwd="";//"jsd1304";

/** 在静态初始化中注册驱动,驱动不需要重复注册,所以静态初始化最合适注册驱动 */

static{ try { /** 加载配置文件,读取配置信息 db.properties中的内容见5.3 */

properties.load(BaseDAO.class.getClassLoader()

.getResourceAsStream("day01pm/dao/db.properties"));

System.out.println(properties.getProperty("jdbc.driver"));//可输出检查一下

/** 获取值 */

driver=properties.getProperty("jdbc.driver");

url=properties.getProperty("jdbc.url");

user=properties.getProperty("jdbc.user");

pwd=properties.getProperty("jdbc.pwd");

//加载驱动,并注册到DriverManager,详见2.4

Class.forName(driver);//反射机制

} catch (Exception e) { e.printStackTrace();

throw new RuntimeException(e);//若注册失败,我们要通知调用者 }

}

/** 获取数据库连接对象Connection 让外界去捕获异常,连接不上数据库该怎么办?*/

protected static Connection getConnection() throws SQLException{

return DriverManager.getConnection(url,user,pwd); }

/** 将给定的数据库连接关闭 */

protected static void closeConnection(Connection conn){

if(conn!=null){  try { conn.close(); } catch (SQLException e) {//catch块可不写内容

  e.printStackTrace(); } } }

step3StudentDAO类(用于操作数据库Student表)并继承BaseDAO

public Student findStudentByName(String name){

Connection conn=null;

try{// Class.forName("oracle.jdbc.driver.OracleDriver");

   // conn=DriverManager.getConnection(

   // "jdbc:oracle:thin:@192.168.0.20:1521:tarena","jsd1304","jsd1304");

   //通过父类BaseDAOgetConnection()方法获取数据库连接

   conn=getConnection();

   // Statement state=conn.createStatement();//使用Statement

   // String sql="SELECT * FROM student_chang WHERE name='"+name+"'";

   String sql="SELECT * FROM student_chang WHERE name=?";

   PreparedStatement state=conn.prepareStatement(sql);//使用PreparedStatement

   state.setString(1, name);//给第一个问号赋值

/** 根据用户名查询该用户信息,并将这条数据转化为一个Student对象并返回 */

   // ResultSet rs=state.executeQuery(sql);//使用Statement

   ResultSet rs=state.executeQuery();//使用PreparedStatement时,不需要参数了

   if(rs.next()){ Student student=new Student(); student.setId(rs.getString("id"));

     student.setName(rs.getString("name"));   student.setAge(rs.getInt("age"));

     student.setSex(rs.getString("sex"));    return student; }

   }catch(Exception e){   e.printStackTrace();

   }finally{

   // if(conn!=null){  try {  conn.close();  } catch (SQLException e) {

   // e.printStackTrace(); }     }

   //调用父类的关闭连接方法

   closeConnection(conn);    }

   return null;    }

   /** 持久化Student对象,即将Student对象的数据保存到数据库中 */

   public boolean saveStudent(Student student){

   Connection conn=null;

   try{ // Class.forName("oracle.jdbc.driver.OracleDriver");

   // conn=DriverManager.getConnection(

   // "jdbc:oracle:thin:@192.168.0.20:1521:tarena","jsd1304","jsd1304");

       //通过父类BaseDAOgetConnection()方法获取数据库连接

   conn=getConnection();

   // Statement state=conn.createStatement();

   // String sql="INSERT INTO student_chang VALUES(" +

   // "sys_guid()," +

   // "'"+student.getName()+"',"+

   // student.getAge()+","+

   // "'"+student.getSex()+"'"+

   // ")";

/** 预编译的SQL,将SQL中变化的内容用“?”代替,然后通过PreparedStatement执行该SQL时用给定的参数代替?,来达到插入不同数据的目的,预编译SQL更像是一个格式或者模版 */

   String sql="INSERT INTO student VALUES(sys_guid(),?,?,?)";//问号上不考虑类型,就是占位

   PreparedStatement state=conn.prepareStatement(sql);

   state.setString(1, student.getName());//将第一个问号替换为学生的姓名

   state.setInt(2, student.getAge());

   state.setString(3, student.getSex());

   if(state.executeUpdate()>0){   return true;    }//使用PreparedStatement

   // if(state.executeUpdate(sql)>0){ return true; }//使用Statement

   }catch(Exception e){    e.printStackTrace();

   }finally{

   // if(conn!=null){  try {  conn.close();  } catch (SQLException e) {

   // e.printStackTrace(); }     }

   //调用父类的关闭连接方法

   closeConnection(conn);    }

   return false;    }

 

step4StudentService类(业务逻辑类)

private StudentDAO studentDAO=new StudentDAO();

public void reg(String name,int age,String sex){

/** 必要的验证 */

if(name==null||"".equals(name)){ System.out.println("名字不能为空!");

}else if(age<1||age>99){ System.out.println("年龄只能在199之间!");

}else if(!"1".equals(sex)&&!"0".equals(sex)){System.out.println("性别只能为01");

}else if(studentDAO.findStudentByName(name)!=null){

System.out.println("该用户已存在!");

}else{//1 将用户输入的信息转化为一个Student对象

Student student=new Student(); student.setName(name);

student.setAge(age); student.setSex(sex);

//2 将该对象交给DAO进行持久化 3根据保存结果通知用户

if(studentDAO.saveStudent(student)){ System.out.println("注册成功!");

}else{ System.out.println("注册失败!"); } } }

public void findStudentByName(String name){

//对用户输入的信息进行必要的判断,null和空字符串

if(name!=null && !"".equals(name)){

//DAO获取学生信息

Student student=studentDAO.findStudentByName(name);

if(student!=null){

System.out.println("学生:"+student.getName()+"年龄:"+student.getAge());

}else{ System.out.println("查无此人!"); } } }

step5:测试类

String studentName="chang";

StudentService service=new StudentService();

service.findStudentByName(studentName);

String studentName1="changyb";

int age=16;

String sex="1";

StudentService service1=new StudentService();

service1.reg(studentName1, age, sex);

六、
批处理

6.1批处理的优点

一个批处理是被发送到数据库以作为单个单元执行的一组更新语句。这降低了应用程序和数据库之间的网络调用。相比单个SQL语句的处理,处理一个批处理中的多个SQL语句是一种更为有效的方式。

6.2 JDBC批处理API

1addBatch(String sql)Statement类的方法,“多次调用”该方法可以将给定的“多条”SQL语句添加到Statement对象的命令列表中缓存(每调用一次缓存一条SQL)。

2addBatch()PreparedStatement类的方法,“多次调用”该方法可以将“多条”预编译的SQL语句添加到PreparedStatement对象的命令列表中缓存(每调用一次缓存一条SQL)。

3executeBatch():把Statement对象或PreparedStatement对象命令列表中缓存的所有SQL语句一次性提交给数据库进行处理。这样可以有效的减少网络通信带来的性能消耗。

4clearBatch():清空当前SQL命令列表中缓存的所有SQL语句。

6.3案例:详见8.4案例step7

七、
事务处理

7.1事务特性ACID

1)原子性(atomicity):事务必须是原子工作单元;对其数据修改,要么全都执行,要么全都不执行。

2)一致性(consistency):事务在完成时,必须使所有的数据都保持一致状态。

3)隔离性(isolation):由并发事务所作的修改必须与任何其他并发事务所作的修改隔离。

4)持久性(durability):事务完成之后,它对于系统的影响是永久性的。

7.2 JDBC中对事务的支持(API

1JDBC默认是每执行一条SQL语句都会提交事务。这对于批量处理数据来说性能开效果大。而且不满足事务本该管理的原则。

2conn.setAutoCommit(false);//不自动提交

3conn.commit();//提交事务

4conn.rollback();//回滚事务

八、
DAO事务封装

8.1 ThreadLocal原理

1Java提供了一个类ThreadLocal:用于实现线程内的数据共享,即对于相同的程序代码,多个模块(方法)在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。

2)数据共享是根据线程区分的。不同线程间不共享数据。

3)原理分析:

ThreadLocal内部很简单:就是维护者一个HashMap,其中key存放的是每一个线程,value存放这个线程在不同模块间要共享的数据。每一个ThreadLocal实例,只能保存一个线程的一个共享数据,不同线程看到的应该是同一个ThreadLocal实例,但是获取的是不同的数据,因为线程间不共享。每个线程在不同模块中共享数据。比如:BaseDAO中,getConnection()commin()rollback()closeConnection()这些方法,都共享同一个Connection实例。

  HashMap hashMap = new HashMap();

  void set(Object obj){ hashMap.put(Thread.currentThread(),obj); }

  Object get(){ return hashMap.get(Thread.currentThread()); }

4)多线程共享数据:可用单例模式,但要加锁。

8.2原理图

 

8.3 ThreadLocal核心API

1public T get():用于获取线程共享的数据。

2public void set(T value):用于保存线程共享的数据。

3public void remove():移除线程中保存的数据。

注意事项:使用remove()key也没了,使用set(null)key还保留。

8.4案例:登录系统(使用ThreadLocal实现连接共享)

step1:添加实体类UserInfo,并设置相应的get/set方法

private static final long serialVersionUID=1L;

private String id; private String name; private String password;

private int age; private String sex; private String email;

 

step2:修改5.2案例中的BaseDAO中的getConnection()方法

protected static Connection getConnection() throws SQLException{

/** 当一个线程调用该方法要获取连接时,我们先检查之前这个线程是否已经获取过一个连接了,若有就不再创建了 */

Connection conn=localConn.get();//get用于获取线程共享的数据

if(conn==null){//若是空的,说明这个线程第一次获取连接

//创建和数据库的连接

conn=DriverManager.getConnection(url,user,pwd);

        //将创建出来的连接放入线程共享中,set方法用于保存线程共享的数据

localConn.set(conn); }

return conn; }

step3:修改5.2案例中的BaseDAO中的closeConnection()方法

protected static void closeConnection(){

try{ Connection conn=localConn.get();

if(conn!=null){ conn.close();//关闭连接

//连接关闭后,这个连接就没有存在的意义了,应该从线程共享中将其删除!

localConn.remove();//key也没了,或者localConn.set(null);key还保留

}

}catch(Exception e){ e.printStackTrace(); } }

step4:添加begin方法:开始事务

protected static void begin(){

try{ Connection conn=localConn.get();

if(conn!=null){  conn.setAutoCommit(false);//取消事务的自动提交   }

}catch(Exception e){ e.printStackTrace(); } }

step5:添加commit方法:提交数据到数据库。

protected static void commit(){

/** 先去线程共享中看看当前调用该方法的线程是否有共享过Connection,若有,就提交事务。不需要UserInfoDAO再告诉我提交哪个conn,即不用再传参数 */

Connection conn=localConn.get();

if(conn!=null){  try { conn.commit();  } catch (SQLException e) {

e.printStackTrace(); }     } }

step6:添加rollback方法,回滚事务。

protected static void rollback(){

/** 不需要UserInfoDAO再告诉我回滚哪个conn,即不用再传参数 */

Connection conn=localConn.get();

if(conn!=null){  try { conn.rollback();  } catch (SQLException e) {

e.printStackTrace(); } } }

step7:添加UserInfoDAO类,并继承BaseDAO

private static final String INSERT="INSERT INTO userinfo_chang(" +

"id,name,password,age,sex,email) VALUES(sys_guid(),?,?,?,?,?)";

/** 保存给定的所有用户信息 */

public boolean save(List<UserInfo> userInfos){

Connection conn=null;

try{ conn=getConnection(); conn.setAutoCommit(false);//禁止自动提交

PreparedStatement state=conn.prepareStatement(INSERT);

long start=System.currentTimeMillis();

// for(int i=0;i<50;i++){//可快速插入50条记录

// state.setString(1,"test"+ i); state.setString(2,"12345"+ i);

// state.setInt(3,22); state.setString(4,"1");

// state.setString(5,"test"+ i +"mail.com");

// //state.executeUpdate();//每执行一次都提交一次事务,涉及到硬件的写操作

// state.addBatch();//Statement需要把sql语句写入参数列表

// }

  for(UserInfo userinfo:userInfos){//读取集合中的记录,并存入数据库,集合不能为空,为空则不执行了。

  state.setString(1,userinfo.getName()); state.setString(2,userinfo.getPassword());

  state.setInt(3,userinfo.getAge()); state.setString(4,userinfo.getSex());

  state.setString(5,userinfo.getEmail());

  //state.executeUpdate();//每执行一次都提交一次事务,涉及到硬件的写操作

  state.addBatch();//Statement需要把sql语句写入参数列表

  }

state.executeBatch();//批处理执行之前缓存的所有SQL

//conn.commit();

commit(); long end=System.currentTimeMillis();

System.out.println("耗时:"+(end-start)+"毫秒");

return true;//返回true,告知调用者保存成功

}catch(Exception e){

e.printStackTrace();

// if(conn!=null){//若执行保存过程中出错,要回滚事务

// //conn.rollback(); }

rollback();

}finally{ //closeConnection(conn);

closeConnection(); }

return false; }

/** 创建表 */

public void createTable(){

Connection conn=null;

try{ conn=getConnection();

Statement state=conn.createStatement();

String sql="CREATE TABLE userinfo_chang(" +

"id VARCHAR2(36) PRIMARY KEY,name VARCHAR2(30)," +

"password VARCHAR2(50),age NUMBER(2)," +

"sex VARCHAR2(2),email VARCHAR2(50))";

if(!state.execute(sql)){ System.out.println("创建完毕"); }

} catch(Exception e){ e.printStackTrace();

}finally{ if(conn!=null){ closeConnection(conn); } } }

九、
分页查询

9.1分页查询的基本原理

  Connection conn = getConnection();

  PreparedStatement state= conn.prepareStatement(sql);

  int start = rowsPerPage * (page -1)+1;//每页的开始

  int end = start + rowsPerPage;//每页的结束

  state.setInt(1,end); state.setInt(2,start);

  ResultSet rs =state.executeQuery();

  List<Service> list = new ArrayList<Service>();

  while(rs.next()){ list.add(toService(rs)); }//把每条数据转成对象后添加

9.2为何使用分页查询

1)每次只向数据库要求一页的数据量,频繁访问数据库,内存压力小,适合大数据量。

2)数据库中的表可能会存储若干数据,若我们一次性将所有数据获取显然是不理智的。这可能产生很多坏处,比如占用内存过大,而对于用户而言,数据过于多也不利于查看等。为此,我们可以将数据分批次的检索出来。既可以节省资源,也利于用户查看。不同数据库对分页支持的SQL语句不尽相同。所以,使用不同的数据库,我们要适应当前数据库对分页语句的定义。当然,hibernate屏蔽了数据库分页的差异。可以让我们很方便的使用统一方式进行分页查询。

9.3 Oracle分页查询SQL语句

1Oracle中的分页使用了一个字段rownum,使用该字段对查询的行数进行限制,从而达到获取某一区间的数据。实现分页查询。

2rownum它是oracle系统顺序分配的查询返回的行的编号,查询到第一条数据rownum返回第一行分配的行号1,查询到第二条数据rownum返回第二行分配的行号2,依此类推。也就是说行号是查询到数据后才产生的。

3)起始、结束位置计算:

每页的起始位置start=每页显示的记录数rows ×(当前要请求的页数page-1+1

每页的结束位置end=每页的起始位置start + 每页显示的记录数rows(或end=page * rows 含头不含尾 java的一种习惯,不是必须的算法)。比如:

  select

  id,account_id,host,user_name,login_password,status,create_date,pause_date,close_date,cost_id

  from (select id,account_id,host,user_name,login_password,status,create_date,pause_date,

   close_date,cost_id,rownum r from service where rownum < ?) where r >=?

注意事项:子查询不能写 * ,写了不会有rownum,记得给rownum起列别名!

9.4 MySQL分页查询SQL语句

select * from table limit start,pageSize

注意事项:start0开始,pageSize为每页的条数。

9.5“假”分页

1)一次性把数据全部取出来放在缓存中,根据用户要看的页数(page)和每页记录数(pageSize),计算把哪些数据输出显示。

2)只访问数据库一次,第一次取数比较慢,以后每页都从缓存中取,比较快。

3)比较适合小数据量,如果数据量大,对内存压力比较大。

4)一次性将数据库数据读入结果集,每次查看指定的页时,要求结果集的指针能够跳到指定的行,即指针能够跳到整个结果集的任一位置。

9.6案例:分页查询

step1:在8.4案例中step7UserInfoDAO类添加分页查询方法和根据用户名、密码获取用户信息方法

/** 分页查询用户信息。page:第几页。rows:每页显示的条数 */

public List<UserInfo> findPaging(int page,int rows){

try{/** 根据页数和每页显示的条数,计算起始行号和结束行号 */

int start=(page-1)*rows+1;

int end=page*rows;//含头不含尾 java的一种习惯,不是必须的算法

/** 执行分页查询的OracleSQL语句,这里我们要进行两次查询,第一次先将后区间定位,第二次再将前区间定位。从而获取区间中的数据。 */

//子查询不能写 * ,写了不会有rownum,注意 r 后有一个空格

String sql="SELECT * FROM " +

 "(SELECT id,name,password,age,sex,email,rownum r " +

  "FROM userinfo_chang WHERE rownum < ?) WHERE r >=? ";

//获取预编译Statement

//Connection conn=getConnection();

//PreparedStatement state=conn.prepareStatement(sql);

//现在不需要关conn了,由BaseDAO去关(统一线程数据共享)

PreparedStatement state=getConnection().prepareStatement(sql);//上面二合一

state.setInt(1, end);//设置后区间(第一个问号)

state.setInt(2, start);//设置前区间(第二个问号)

ResultSet rs=state.executeQuery();//获取结果集

ArrayList<UserInfo> userinfos=new ArrayList<UserInfo>();

/** 这里我们可以在循环外面创建一个引用变量,从而节省循环带来的不必要的内存开销。但是绝对不能在循环外面创建一个对象,在循环里重复设置内容,否则看到的效果就是虽然查询出来了若干数据,但是集合中保存的却是最后一条数据,而且保存了若干次而已(所有引用指向最后一个对象,所以内容相同且为最后一条数据的)。 */

UserInfo userinfo=null;

while(rs.next()){//引用变量可同一个,但对象不能同一个(即定义在循环外)

userinfo=new UserInfo(); userinfo.setId(rs.getString("id"));

userinfo.setName(rs.getString("name"));

userinfo.setPassword(rs.getString("password"));

userinfo.setAge(rs.getInt("age")); userinfo.setSex(rs.getString("sex"));

userinfo.setEmail(rs.getString("email")); userinfos.add(userinfo); }

rs.close(); state.close(); return userinfos;

}catch(Exception e){ e.printStackTrace(); throw new RuntimeException(e);

}finally{ closeConnection(); }

//return null; }

//catch块写了throw new RuntimeException(e);就不用写return语句了,因为执行不到

/** 根据用户名、密码获取用户信息 */

public UserInfo findUserInfoByNameAndPwd(String name,String password){

try{ String sql="SELECT * FROM userinfo_chang " +

"WHERE name='"+name+"' AND password='"+password+"' ";

Statement state=getConnection().createStatement();

ResultSet rs=state.executeQuery(sql); UserInfo userinfo=null;

if(rs.next()){ userinfo =new UserInfo(); userinfo.setId(rs.getString("id"));

  userinfo.setName(rs.getString("name"));

userinfo.setPassword(rs.getString("password"));

userinfo.setAge(rs.getInt("age")); userinfo.setSex(rs.getString("sex"));

userinfo.setEmail(rs.getString("email")); }

rs.close(); state.close(); return userinfo;

}catch(Exception e){ e.printStackTrace(); throw new RuntimeException(e);

}finally{ closeConnection(); } }

step2main方法测试

public static void main(String[] args){

UserInfoDAO dao=new UserInfoDAO();

// dao.createTable();//建表

// dao.save(new ArrayList());//快速插入数据

  List<UserInfo> list=dao.findPaging(2, 10);

  for(UserInfo user:list){  System.out.println(user.getName()); } }

step3main方法添加如下语句,测试SQL注入

/** SQL注入攻击:对于SQL语句"SELECT * FROM userinfo WHERE name='chang' AND password='123456'"

把密码修改为当前形式:AND password='1234' OR '1'='1' 即拼写部分为:

  1234' OR '1'='1  呢?

(第21最后无单引号,拼的字符串里有,第一个密码同理)则发现也可登录成功! */

// UserInfo userinfo=new UserInfo();//添加用户

// userinfo.setName("chang"); userinfo.setPassword("123456");

// List<UserInfo> list=new ArrayList<UserInfo>();

// list.add(userinfo); dao.save(list);

UserInfo user=dao.findUserInfoByNameAndPwd("chang","123456");

//dao.findUserInfoByNameAndPwd("chang", "1234' OR '1'='1");//SQL注入攻击,则发现也可登录成功!

if(user!=null){ System.out.println("欢迎你:"+user.getName());

}else{ System.out.println("登录失败!"); }

 

posted @ 2020-08-02 06:57  woshiwangding  阅读(231)  评论(0)    收藏  举报