Java EE笔记 (1) JDBC

JDBC(全称为:Java Data Base Connectivity)
共两个包:
java.sql
javax.sql
工作流程:
应用程序—JDBC—MySQL驱动或Oracle驱动—MySQL数据库或Oracle数据库
MySQL驱动:mysql-connector-java-5.0.8-bin.jar
Oracle驱动:ojdbc14.jar

一、JDBC工作六大步骤:
1.加载驱动
DriverManager.registerDriver("com.mysql.jdbc.Driver") 
不推荐,这种形式会造成JVM虚似机内存中产生两个一样的Driver对象。
Class.forName("com.mysql.jdbc.Driver");
推荐这种方式,不会对具体的驱动类产生依赖。

2.建立连接
Connection conn = DriverManager.getConnection(url,user,pass); //user与pass为登陆数据库的用户名与密码
也可采用这种方式
Connection conn = DriverManager.getConnection(url?user=root&&password=root); //用户名与密码以参数带在后面
协议:子协议://主机:端口/数据库名
MySQL的url:jdbc:mysql://localhost:3306/数据库名
Oracle的url:jdbc:oracle:thin:@localhost:1521:数据库名
SQLServer的url:jdbc:microsoft:sqlserver//localhost:1433; DatabaseName=数据库名

3.创建执行SQL的语句
(1)采用Statement实现,有被SQL注入的危险,如:('or 1=1 or name=') ,且可能造成数据库缓冲区溢出:
Statement st = conn.createStatement();

(2)采用PreparedStatement实现,此类安全,可对SQL进行预编译:
String sql = "insert into 表名(id,name,birthday,mytext,myblob) values(?,?,?,?,?);
PreparedStatement st = conn.prepareStatement(sql);

conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);//该常量指示生成的键可以获取。
st.setInt(1,1);  //对应第1个?号
st.setString(2,"wudi");  //对应第2个?号
//对应第3个?号,日期存入数据库时要转成sql类型的Date,getTime()方法将日期转成毫秒数字
st.setDate(3, new java.sql.Date(new java.util.Date().getTime()));  
//对应第4个?号,存大文本
URL url = 类.class.getClassLoader().getResource("a.txt");//得到文件虚拟地址
File file = new File(url.getPath()); //得到文件真实路径并用file连接
st.setCharacterStream(4, new FileReader(file), (int) file.length());
//对应第5个?号,存二进制文件
URL url = 类.class.getClassLoader().getResource("a.jpg");//得到文件虚拟地址
File file = new File(url.getPath()); //得到文件真实路径并用file连接
st.setBinaryStream(2, new FileInputStream(file), (int) file.length());  //MySql数据库存的方式

4.执行语句
ResultSet rs = st.getGeneratedKeys();  //得到插入行的主键,只对insert有用
ResultSet rs = st.excute(sql);       //任意操作
ResultSet rs = st.excuteQuery(sql);  //查询时
ResultSet rs = st.excuteUpdate(sql); //增删改时
//可实现批处理sql语句
Statement.addBatch(sql)  
优点:可以向数据库发送多条不同的sql语句。
缺点:sql语句没有预编译
PreparedStatement.addBatch(sql)
优点:发送的是预编译后的SQL语句,执行效率高。
缺点:只能应用在sql语句相同,但参数不同的批处理中。

(1)小例子:采用Preparedstatement.addBatch(sql)方法实现sql语句批处理,
String sql = "insert into tbatch(id,name,password) values(?,?,?)";
st = conn.prepareStatement(sql);
for(int i=1;i<500003;i++){
  st.setInt(1, i);
  st.setString(2, "aa" + i);
  st.setString(3, "1111" + i);
  //程序执行到此,st对象里面已经是一条完整的sql,可以加到 batch里面去了
  st.addBatch();
  if(i%1000==0){//batch最大可容1000条sql语句
      st.executeBatch();  //执行来1次batch里的所有sql,
      st.clearBatch();  //清空batch里的所有sql
  }
}

(2)小例子:在Oracle数据库中实现存二进制文件,注意:这些操作需开启事务。
//1.向列中存一个指针
String sql = "insert into tblob(id,image) values(1,empty_blob())";
PreparedStatement st = conn.prepareStatement(sql);
st.executeUpdate();
//2.把指针查询出来
sql = "select 二制文件列名 from 表 where id=1 for update";  //for update是为了锁定这一行,为了不产生并发冲突
st = conn.prepareStatement(sql);
rs = st.executeQuery();
if(rs.next()){
  BLOB blob =  (BLOB) rs.getBlob("image");
  OutputStream out = blob.getBinaryOutputStream();
  URL url = 类.class.getClassLoader().getResource("a.jpg");//得到文件虚拟地址
  File file = new File(url.getPath()); //得到文件真实路径并用file连接
  FileInputStream in = new FileInputStream(file);
  byte buffer[] = new byte[1024];
  int len = 0;
  while((len=in.read(buffer))>0){
     out.write(buffer, 0, len);
  }
}
st.executeBatch();

5.处理执行结果
rs.Previous()://移动到前一行
rs.absolute(1)://移动到指定行
rs.beforeFirst():移动ResultSet的最前面(表头)
rs.afterLast() ://移动到ResultSet的最后面(表尾)
if(rs.next){    //指向第一条,下次执行指向第二行
第一种方法,得到的是个Object对象
   rs.getObject("id");   //取出该行列名为id的值
   rs.getObject(1);  //取出该行第一列的值
第二种方法,得到的是对应类型的值
   rs.getBoolean()       //BIT(数据库中对应的类型)
   rs.getByte()          //TINYINT
   rs.getShort()         //SMALLINT
   rs.getInt()           //int
   rs.getLong()          //BIGINT
   rs.getString()        //CHAR,VARCHAR,LONGVARCHAR
   //用流方式取出数据库中的大文本 //Text,longText
   Reader reader = rs.getCharacterStream("大文本列名"或列号);
   或
   Reader reader = rs.getClob("大文本列名"或列号).getCharacterStream();
   char buffer[] = new char[1024];
   int len = 0;
   FileWriter writer = new FileWriter("c:\存放取出数据的文件名.txt");
   while((len = reader.read(buffer))>0){
      writer.write(buffer,0,len);
   }
   //用流方式取出数据库中的二进制文件 //Blob,LongBlob
   InputStream in = rs.getBinaryStream("二进制文件存放列名"或列号);
   或
   InputStream in = rs.getBlob("二进制文件存放列名"或列号).getBinaryStream();
   byte buffer[] = new byte[1024];
   int len = 0;
   FileOutputStream out = new FileOutputStream("c:\1.jpg");
   while((len=in.read(buffer))>0){
       out.write(buffer,0,len);
   }
   rs.getDate()          //DATE,得到的是java.sql.Date类型
   rs.getTime()          //TIME,得到的是java.sql.Time类型
   rs.getTimestamp()     //TIMESTAMP,得到的是java.sql.Timestamp类型
}

6.释放资源(以下代码要放在finally中)
rs.close();
st.close();
conn.close();

二、数据分页显示(customer工程):

MySql分页sql语句
select * from customer limit ?,?
Oracle分页sql语句
select * from(
    select rownum r_, row_.*  from(
select * from student order by id
    )row_ where rownum <=5  //5为起始索引位置
)where r_>=1  //1为结束索引位置

1.完成分页的dao支持
1.获取总纪录数的方法
2.根据起始位置,得到某一页数据的方法

2、写响应用户分页显示的servlet
1.调用dao获得总纪录数
2.得到用户查看哪一页
3.把上面两个信息,传递给page,并使用page对象负责完成计算逻辑
4.根据page计算出来的该页在数据库的起始位置,凋用dao获得页面数据
5.把封装了页面数据的list集合,封装到page对象中
6.把page对象,保存到request域中,交给jsp显示

3、jsp显示page对象数据
1.要显示page里面封装的list集合(即页面数据)
2.foreach循环显示page里面封装的开始页码和结束页码,以在jsp页面中显示页号

4、page对象完成什么计算逻辑?
1.要根据总纪录数,算出总页数
2.要根据用户所要查看页,算出该页的数据应该从数据库哪个位置开始取
3.根据用户所要查看的页,计算jsp页面中,页号的起始码和结束码
4.page对象提供一个list集合,用于封装页面数据
修改的做法
1.回显用户的数据
1.点修改,访问servlet:FindCustomer,它负责取出需要修改的用户的数据
2.FindCustomer这个servlet把用户数据交给jsp回显
3.用户可以在jsp回显页面上修改用户数据,提交给EditCoustomer,EditCoustomer这个servlet负责用最新的用户数据,覆盖原有数据库的数据

三、JDBC调用存储过程步骤:

1.得到CallableStatement,并调用存储过程
CallableStatement cs = conn.prepareCall("{call 方法(?, ?)}");

2.设置参数,注册返回值,得到输出
cs.registerOutParameter(2, Types.VARCHAR);
cs.setString(1, "abc");
cs.execute();
cs.getString(2);

四、手动写一个连接池

1.写一个自已的类实现java.sql.DataSource接口

 

五、常用数据库连接池(也叫数据源),实现了DataSoruce

1.DBCP 是 Apache 软件基金组织下的开源连接池实现
在工程中增加如下两个 jar 文件:
Commons-dbcp.jar:连接池的实现
Commons-pool.jar:连接池实现的依赖库
//dbcpconfig.properties为DBCP的配置文件
InputStream in = JdbcUtil.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties prop = new Properties();
prop.load(in);
BasicDataSourceFactory factory = new BasicDataSourceFactory();
DataSource ds = factory.createDateSource(prop);  //得到数据源对象
ds.getConnection();  //返回Connection,供自已手写的dao调用
ds.getDataSource();  //DataSource,供DBUtils框架调用

2.C3P0
在工程中增加如下一个 jar 文件:
c3p0-0.9.1.2.jar:连接池的实现
ComBolDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");  //装载mysql数据库驱动
ds.setJdbcUrl("jdbc:mysql://localhost:3306/数据库名"); //注册
ds.setUser("数据库用户名");  
ds.setPassword("数据库密码");
ds.setMaxPoolSize(50);  //设置最大连接数
ds.setMinPoolSize(10);  //设置最小连接数
ds.setInitialPoolSize(20); //设置初始连接数
ds.getConnection();  //返回Connection,供自已手写的dao调用
ds.getDataSource();  //DataSource,供DBUtils框架调用

3.Tomcat的连接池
(1)、此种配置下,驱动jar文件需放置在tomcat的lib下
在WebRoot/META-INF/建军一个context.xml文件,写如下代码:
<Context>
  <Resource name="jdbc/任意名称a"  //为了让程序可以拿到这个配置文件
            auth="Container"
            type="javax.sql.DataSource"
            username="数据库用户名"
            password="数据库密码"
            driverClassName="com.mysql.jdbc.Driver"
            url="jdbc:mysql://localhost:3306/数据库名"
            maxActive="8"
            maxIdle="4"/>
</Context>

(2)、用JNDI(Java Naming and Directory Interface)技术拿到Tomcat的连接池:
在javax.naming包下,其核心API为Context,它代表JNDI容器,其lookup方法为检索容器中对应名称的对象。
Context initCtx = new InitialContext(); //jndi初始化
Context envCtx = (Context) initCtx.lookup("java:comp/env");//得到tomcat中的JNDI容器
DataSource ds = (DataSource) envCtx.lookup("jdbc/任意名称a"); //拿到了context.xml中配置的数据库连接池
ds.getConnection();  //返回Connection,供自已手写的dao调用
ds.getDataSource();  //DataSource,供DBUtils框架调用

 

六、事务

1.事务的概念:
事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。
数据库开启事务命令
start transaction  开启事务
Rollback  回滚事务
Commit   提交事务
JDBC控制事务语句
//关闭JDBC默认提交方式,让多条SQL在一个事务中执行
Connection.setAutoCommit(false);  
//设置事务回滚点
Savepoint sp = conn.setSavepoint();
//回滚到上面设置的回滚点
Connection.rollback(sp);
//提交,回滚后必须要提交
Connection.commit();

2.事务的特性(ACID):
原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。?
一致性(Consistency):事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
隔离性(Isolation):事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

3.事务的隔离性:
脏读:指一个事务读取了另外一个事务未提交的数据。
不可重复读:在一个事物内读取表中的某一行数据,多次读取结果不同。
和脏读的区别是:脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据。
虚读:是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
数据库共定义了四种隔离级别:
Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)    例:conn.TRANSACTION_SERIALIZABLE
Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)
Read committed:可避免脏读情况发生(读已提交)。
Read uncommitted:最低级别,以上情况均无法保证。(读未提交)
数据库存中对事务隔离性的操作语句:
set   transaction isolation level 设置事务隔离级别
select @@tx_isolation查询当前事务隔离级别

4.程序中设置隔离级别(conn为Connection类),此操作要在开启事务之前:
conn.setTransactionIsolation(conn.TRANSACTION_SERIALIZABLE);
conn.setTransactionIsolation(conn.TRANSACTION_REPEATABLE_READ);
conn.setTransactionIsolation(conn.TRANSACTION_READ_COMMITTED);
conn.setTransactionIsolation(conn.TRANSACTION_READ_UNCOMMITTED);

 

七、元数据-DataBaseMetaData

1.返回数据库的定义信息:
DataBaseMetaData db = Connection.getDatabaseMetaData():返回数据库元数据对象
db.getURL():返回一个String类对象,代表数据库的URL。
db.getUserName():返回连接当前数据库管理系统的用户名。
db.getDatabaseProductName():返回数据库的产品名称。
db.getDatabaseProductVersion():返回数据库的版本号。
db.getDriverName():返回驱动驱动程序的名称。
db.getDriverVersion():返回驱动程序的版本号。
db.isReadOnly():返回一个boolean值,指示数据库是否只允许读操作。

2.返回表的定义信息:
ParameterMetaData pd = PreparedStatement.getParameterMetaData():返回代表PreparedStatement对象的元数据
pd.getParameterCount():获得PreparedStatement对象传递的参数个数
pd.getParameterType(1):获得PreparedStatement对象传递的参数中第1个参数的sql类型

3.返回列的定义信息:
ResultSetMetaData rd = ResultSet.getMetaData():获得代表ResultSet对象的元数据对象
rd.getColumnCount():返回resultset对象的列数
rd.getColumnName(1):获得第1列的列名
rd.getColumnTypeName(1):获得第1列的列数据的类型 

八、DBUtils框架

共一个包:
org.apache.commons.dbutils
导jar包:commons-dbutils.jar
DbUtils类的方法(都是静态的):
DbUtils.close(conn,st,rs):关闭Connection、Statement和ResultSet。
DbUtils.closeQuietly(conn,st,rs):关闭Connection、Statement和ResultSet,隐藏异常。
DbUtils.commitAndCloseQuietly(conn): 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。 
DbUtils.loadDriver():装载并注册JDBC驱动程序,如果成功就返回true,不需要捕捉ClassNotFoundException异常。
QueryRunner类的主要方法:
runner.query(conn,sql,params,ResultSetHandler):查询操作,将结果集保存到指定JavaBean对象中
runner.query(sql,params,ResultSetHandler):查询操作,将结果集保存到指定JavaBean对象中
runner.query(conn,sql,ResultSetHandler): 查询操作,不用参数,将结果集保存到指定JavaBean对象中。
runner.update(conn,sql,params):用来执行一个更新(插入、更新或删除)操作。
runner.update(conn,sql):用来执行一个不需要置换参数的更新操作。
ResultSetHandler 接口的实现类:
ArrayHandler:把结果集中的第一行数据转成对象数组。
ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
ColumnListHandler:将结果集中某一列的数据存放到List中。
KeyedHandler(name):将结果集中的每一行数据都封装到一个小Map里,再把这些小map再存到一个另一大map里,其map的key为传进来的key,如name。
MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List

1.增删改操作(CUD):
QueryRunner runner = new QueryRunner(DataSource类);  //得到QueryRunner对象
String sql = "sql语句";  //这里的语句可以是CRUD语句
Object[] arr = {参数列表}; //构建参数对象数组
qr.update(sql,arr);   //将参数传给DBUtils框架,执行增删改操作

2.常用查找操作(R):
User user = runner.query(sql,params,new BeanHandler(user.class)); 
List list = runner.query(sql,params,new BeanListHandler(user.class));

常用O-R Mapping映射工具
Hibernate
Ibatis
Commons DbUtils(只是对JDBC简单封装)

九、CachedRowSet(离线操作数据库)

1.创建CachedRowSet
CachedRowSetImpl cache = new CachedRowSetImpl();

2.填充CachedRowSet
//对查出的结果集提供分页功能
cache.setPageSize(10);  //取10行数据
cache.populate(rs,2);  //从第2行开始取,起始值是1

3.更新CachedRowSet
调用update***()方法后,调用updateRow方法
更新至数据库,调用acceptChanges方法

原文地址:https://www.cnblogs.com/hjl553155280/p/6170192.html