JDBC
第一节课:B/S四层结构的介绍,引入对数据库的分析,为什么要用到JDBC。
表现层:结果展示,数据录入,触发请求
控制层:接收请求,下发数据
业务逻辑:组织业务逻辑,分配任务
数据持久:数据操作
一般数据库的文件只要数据库厂商才知道怎么去操作的,因此开发了数据库服务器,并提供了数据库客户端,用户只需在客户端上操作就直接能操作底层的数据了。但是这一个客户端每个公司的不同,造成了应用的困难,因此需要用一个统一的规范,这就是JDBC的出现。
JDBC规范
所谓规范实际上一套套的接口规定
JDBC只是定义了一套操作数据库的接口,不同数据库公司按照这个接口规范实现了相应的类,因此对于客户来说只要学习JDBC的规范就可以操作不同的数据库了,不同数据库公司实现的JDBC的接口的类库称为该数据库的驱动。
JDBC使用流程
获取数据库连接(一次一般只能连接一个库,对orcale来说只要服务的概念,也就是一个库)
服务器IP,端口(MySQL为3306)
协议(jdbc:mysql)
库或服务名称
执行SQL语句
如果执行的是查询的话,要接收查询结果
断开与数据库的连接
Class.forName(“com.mysql.jdbc.Driver”) //动态引入数据库驱动类 ,而不是直接静态的采用import方法,以此提高程序的可维护性,因为这个名字可以通过xml配置文件读入而得到。
JDBC详细应用
类介绍:
DriverManager 其中的getConnection方法可以得到一个Connection 变量
Connection 其中的createStatement()方法可以得到一个Statement变量
Statement 其中的executeUpdate()可以执行一条SQL语句,该返回值表示影响到了几条记录。executeQuery()进行查询,返回一个ResultSet对象
ResultSet: 其对象一定要关闭,当执行查询完成后,rs指向标题,故在使用时首先要用next方法,移到下一条,并可以通过返回值判断是否结束。之后就可以通过各个get方法得到不同字段的值了。
在引用这些类时,建议使用java.jdbc下面的接口,以提高程序的可移植性,如果用了某个具体数据库的实现类程序可以实现同样的效果但是可移植性不好,要换数据库时需要改动大量的源码!
//这里引用的都是sql的接口类,而不是用mySQL的实现类,主要是为了提高本程序的可移植性,用到了多态!
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
Connection conn = null;
Statement stat = null;
ResultSet set = null;
try {
//通过反射机制动态的引入应用的数据库的驱动
Class.forName("com.mysql.jdbc.Driver");
//建立连接
//先初始化连接参数mysql:jdbc为协议
String url = "jdbc:mysql://127.0.0.1:3306/storagemis";
//所连接的数据库的用户名和密码
String userName = "root";
String passWord = "";
conn = DriverManager.getConnection(url,userName,passWord);
//System.out.println(CreateID.createID());
//利用SQL语句进行数据库操作
//数据插入操作
String sqlStatement = "INSERT INTO storagemis.goods VALUES ('House',"+CreateID.createID()+",560000)";
//上面的sql语句拼接容易出错,因此可以考虑采用其他的方式。下面改进
//创建表
//String sqlStatement = "create table storagemis.test ( ID varchar(5))";
//得到一个jdbc执行语句
stat = conn.createStatement();
//执行该语句
int result = stat.executeUpdate(sqlStatement);
System.out.println("The operation affect "+result+"record");
//数据库查询操作实例
String sqlQuery = "SELECT * FROM storagemis.goods";
//执行查询操作以后返回一个ResultSet的对象
set = stat.executeQuery(sqlQuery);
//遍历查询结果
while(true){
//在最初执行查询后,指针指向标题,为了能够获取数据,一开始应该移动
//指针到第一条记录,其返回结果用来判断是否结束
boolean resultQuery = set.next();
if(resultQuery == false){
break;
}
//通过set的不同方法的表中的值
int ID = set.getInt(2);
//可以通过字段名得到
String goodsName = set.getString("goodsname");
//可以通过字段的编号得到,注意编号从0开始
int goodsPrice = set.getInt(3);
System.out.println("ID:"+ID+"-goodsName is :"+goodsName+" - goodsPrice is: "+goodsPrice);
}
//以下实例是带参数的SQL查询
String sqlParm = "update storagemis.goods set goodsname ='telphone' where goodsID = ?;";
// 通过connection的prepareStatement()方法建立一个PreparedStatement类(这是statement的子类)的对象,这表示预备sql
PreparedStatement pstatement = conn.prepareStatement(sqlParm);
//进行参数传递,下面的语句表示为预备参数的第一个?传递一个“1”参数
//这里用的setlong,如果是传递的字符串的话,就用setString()
pstatement.setLong(1,2);
//执行sql语句,但是千万注意不要再带参数,否则将出现执行错误,因为sal语句中有?,而不带参数的
//的execute则表示对PreparedStatement的对象执行。
pstatement.execute();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//注意一定要关闭ResultSet
try {
set.close();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//关闭数据库连接
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
JDBC 带参数的SQL JDBC元数据
元数据 Meta Data
本身固有的特性称为元数据
元数据的分类:
数据库的元数据:数据库的名称,版本
查询结果的元数据:字段
通过DatabasedMetaData类的方法得到数据库的元数据,该类的变量有connection.getMetaData( )方法
通过ResultSet类的getMtaData()方法得到一个ResultSetMetaData类的实例,再功过该实例的不同方法得到查询结果的元数据。
练习:双击运行,让用户输入有关数据库的信息,数据库驱动,用户名,密码,数据库。对于查询结果能够以表格的方式表现。 并进行程序的实施!
GetMetaDate.java
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
public class GetMetaDate {
/**
*
* 本实例主要是为了演示获的元数据
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
//建立链接
String databaseDriver = "com.mysql.jdbc.Driver";
//利用反射机制动态加载驱动
Class.forName(databaseDriver);
String url = "jdbc:mysql://127.0.0.1:3306/storagemis";
String userName ="root";
String passWord = "";
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
ResultSetMetaData rsMetaData = null;
//建立链接
try{
conn = DriverManager.getConnection(url,userName,passWord);
//建立好连接后就可以获得有关数据库本身的元数据信息
DatabaseMetaData dbmd = conn.getMetaData();
//从而可以根据DatabaseMetaDate得到关于数据库的各元个信息
String DatabaseProductName = dbmd.getDatabaseProductName();
String DatabaseProductVersion = dbmd.getDatabaseProductVersion();
String DriverName = dbmd.getDriverName();
System.out.println("DatabaseProductName is :"+DatabaseProductName);
System.out.println("DatabaseProductVersion is :"+DatabaseProductVersion);
System.out.println("DriverName is :"+DriverName);
//建立语句,获得查询结果的个数据元信息
String sql = "SELECT * FROM storagemis.goods";
//利用connection得打一个statement
stat = conn.createStatement();
rs = stat.executeQuery(sql);
//获得查询结果的元数据,比如行数,列数,字段名
rsMetaData = rs.getMetaData();
//再通过rsMetaData得到具体的元数据
//得到该结果集中的列数
int columnCount= rsMetaData.getColumnCount();
//在通过该列数得到每一列的名字和大小
for(int i = 1; i <= columnCount ; i ++){
String columnName = rsMetaData.getColumnName(i);
int columnSize = rsMetaData.getColumnDisplaySize(i);
System.out.println("the size of column "+ columnName+ "is "+ columnSize);
}
}catch(Exception e){
e.printStackTrace();
}finally{
//关闭各个连接
try{
conn.close();
rs.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
DBAnywhere源码:
Main.java
package anyfo.databaseanywhere.src;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Element;
import anyfo.databaseanywhere.util.Channel;
import anyfo.databaseanywhere.util.Param;
import anyfo.databaseanywhere.util.XMLUtil;
public class Main {
private int [] lengthOfField = null;
private List<String []> contentList = new ArrayList<String[]>();
/**
* 解析配置文件,得到数据库链接所需要的参数
* @param filePath
* @return
* @throws Exception
* @author
*/
public Param parseConfig(String filePath) throws Exception{
//根据配置文件路径得到一棵DOM树的根节点
Element root = (Element)XMLUtil.getDOMTree(filePath);
//利用该根节点得到各个子节点的值
String databaseDriver = XMLUtil.getTextValueOfElement(root, "databaseDriver");
String url = XMLUtil.getTextValueOfElement(root, "url");
String userName = XMLUtil.getTextValueOfElement(root, "userName");
String passWord = XMLUtil.getTextValueOfElement(root, "passWord");
Param param = new Param();
param.setDatabaseDriver(databaseDriver);
param.setPassWord(passWord);
param.setUrl(url);
param.setUserName(userName);
return param;
}
/**
* 实现对数据库的链接的建立
* @param param
* @return
* @throws Exception
* @author
*/
public Connection connectDatabase(Param param) throws Exception{
Connection conn = null;
//利用反射机制动态加载数据库的驱动程序
Class.forName(param.getDatabaseDriver());
System.out.println("数据库库正在链接中,请稍等......");
//建立链接
conn = DriverManager.getConnection(param.getUrl(),param.getUserName(), param.getPassWord());
//建立好连接后就可以获得有关数据库本身的元数据信息
System.out.println("您已成功链接到数据库");
DatabaseMetaData dbmd = conn.getMetaData();
//从而可以根据DatabaseMetaDate得到关于数据库的各元个信息
String DatabaseProductName = dbmd.getDatabaseProductName();
String DatabaseProductVersion = dbmd.getDatabaseProductVersion();
String DriverName = dbmd.getDriverName();
System.out.println("您使用的数据库库信息如下:");
//打印所链接的数据库的相关信息
System.out.println("DatabaseProductName is :"+DatabaseProductName);
System.out.println("DatabaseProductVersion is :"+DatabaseProductVersion);
System.out.println("DriverName is :"+DriverName);
return conn;
}
/**
* 实现对更新类操作的处理
* @param conn
* @param cmd
* @throws Exception
* @author
*/
public void update(Connection conn,String cmd)throws Exception {
Statement stat = null;
try{
stat = conn.createStatement();
int result = stat.executeUpdate(cmd);
System.out.println("您的操作已经成功,并且影响到了 "+result+"记录");
}catch(SQLException s){
//打印出失败信息!
System.out.println("您的操作失败"+s.getMessage());
}finally{
stat.close();
}
}
/**
* 数据扫描功能,实现对数据库的内容本地保存
* @param conn 链接
* @param cmd 命令
* @throws Exception
* @author
*/
public void scan(Connection conn,String cmd) throws Exception{
Statement stat = null;
ResultSet rs = null;
ResultSetMetaData rsMetaData = null;
//得到一个jdbc执行语句
stat = conn.createStatement();
//获得查询结果集
rs = stat.executeQuery(cmd);
//获得查询结果的元数据,比如行数,列数,字段名
rsMetaData = rs.getMetaData();
//再通过rsMetaData得到具体的元数据
//得到该结果集中的列数
int columnCount= rsMetaData.getColumnCount();
//创建数组
this.lengthOfField = new int[columnCount];
String [] contentArray = new String[columnCount];
//初始化该长度数组,用各表的字段名字的长度作为初始值,同时把各字段值保留到字符数组中
for(int i = 1 ; i <= columnCount ; i++){
String contentValue = rsMetaData.getColumnName(i);
int size = contentValue.length();
this.lengthOfField[i -1 ] = size;
contentArray[i -1 ] = contentValue;
}
this.contentList.add(contentArray);
//更新长度数组,把具体值中较大的长度放入到长度数组中,并同时把值保留到列表中。
//遍历查询结果
while(true){
//在最初执行查询后,指针指向标题,为了能够获取数据,一开始应该移动
//指针到第一条记录,其返回结果用来判断是否结束
boolean resultQuery = rs.next();
if(resultQuery == false){
break;
}
String [] dataArray = new String[columnCount];
for(int i = 1 ; i <= columnCount ; i++){
String contentValue = rs.getString(i);
int size = contentValue.length();
int max = Math.max( size, this.lengthOfField[ i - 1 ] );
this.lengthOfField[ i - 1 ] = max;
dataArray[i -1 ] = contentValue;
}
this.contentList.add(dataArray);
}
}
/**
* 主要实现格式化显示数据的功能
* @author
*/
public void displayData( ){
int columnCount = this.lengthOfField.length;
for(String [] content:this.contentList){
//该循环打印表头的横线
for(int i = 0 ; i < columnCount ; i ++){
//columnCount决定打印多少个表格框
System.out.print("|");
for(int j = 0 ; j < this.lengthOfField[i];j++){
//这层循环决定打印每个框中的东西
System.out.print("-");
}
}
System.out.print("|\n");
//目前状态已经换行,可以开始打印数据库的内容,需要从contentList取出一个数组来进行打印
int index = 0;
for ( String columnValue: content ) {
System.out.print( "|" );
System.out.print( columnValue );
int size = this.lengthOfField[ index ];
for ( int i = 0 ; i < size - columnValue.length() ; i ++ ) {
//打印剩余的空格
System.out.print( " " );
}
index ++;
}
System.out.print( "|\n" );
}
//打印表尾的横线
for(int i = 0 ; i < columnCount ; i ++){
//columnCount决定打印多少个表格框
System.out.print("|");
for(int j = 0 ; j < this.lengthOfField[i];j++){
//这层循环决定打印每个框中的横线
System.out.print("-");
}
}
System.out.print("|\n");
}
/**
* 查询处理函数,主要完成逻辑流程管理,即:扫描数据库、格式化显示数据
* @param conn
* @param cmd
* @throws Exception
* @author
*/
public void queryPrcocess(Connection conn,String cmd)throws Exception {
//查询操作处理的关键是打印表格
//为了实现这个目的先把所有的数据保存下来,这是一个非常好的思路,把数据库的东西保留到内存中去
//以后处理的时候只需要从内存中读取数据就可以了
//扫描数据,并保留数据,其格式是长度数组保留没列中最大的长度,而内容列表中保留一条记录(其中记录
//用String数组保存
//扫描查询结果,并保留到类的成员变量
this.scan(conn,cmd);
//下面的代码开始按格式显示数据
this.displayData();
//一次查询完成后要清空list中的数据;
this.contentList.clear();
}
/**
* DBanywher的启动方法,主要是实现对输入命令的判断,从而选择不同的执行方法
* @param param
* @throws Exception
* @author
*/
public void start(Param param) throws Exception{
//根据各个参数建立链接,并返回链接建立链接
Connection conn = null;
Channel chan = null;
try{
conn = this.connectDatabase(param);
//等待用户读入数据并进行处理
chan = Channel.getChannel();
//一直等待用户的输入
while(true){
System.out.println("请输入你要对该数据库表进行操作的指令(Exit退出)");
String cmd = chan.readData();
//判断用户是否输入exit以便退出,故要求进行判断,为此先把它转换为小写
cmd.toLowerCase();
if(cmd.equals("exit")==true){
break;
//表示退出系统,否则进行下面的处理
}
//下面要根据输入的命令类型进行不同处理,主要是分为两类,查询和更新操作(包括修改,删除,增加)
//为此要对命令进行区分,可以判断命令的前几个字符来判断
if(cmd.startsWith("select")){
//表示进行查询操作
this.queryPrcocess(conn, cmd);
}else{
//表示进行更新操作
this.update(conn, cmd);
}
}
}catch(Exception e){
e.printStackTrace();
}finally{
chan.closeChannel();
conn.close();
}
}
/**
* 主函数,启动程序
* @param args
* @throws Exception
*
*/
public static void main(String[] args)throws Exception {
// TODO Auto-generated method stub
// 读取配置文件,保存各个参数
Main m = new Main();
//运行此带参数的程序需要选用open run dialog,并输入参数
Param param = m.parseConfig(args[0]);
//开始进行处理
m.start(param);
}
}
Channel.java
package anyfo.databaseanywhere.util;
/**
* 本类主要是实现对数据从显示器上读取的管道的建立
*
*/
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Channel {
private InputStream is;
private InputStreamReader isr;
private BufferedReader br;
//做成一个单利模式
private static Channel chan;
//为实现单例,其构造函数必须是私有的
/**
* 私有化构造器,以实现单例模式
* @author
*/
private Channel(){
this.buildChannel();
}
/**
* 由于构造函数是私有的,因此需要为客户提供一个构建该实例的方法
* @return
* @author
*/
public static Channel getChannel(){
if(Channel.chan == null){
Channel.chan = new Channel();
}
return Channel.chan;
}
/**
* 建立管道,不过这是一个私有方法
* @author
*/
private void buildChannel(){
is = System.in;
isr = new InputStreamReader(is);
br = new BufferedReader(isr);
}
/**
* 关闭管道
* @throws Exception
* @author
*/
public void closeChannel() throws Exception{
is.close();
isr.close();
br.close();
}
/**
* 读数据,这是一个柱塞是方法
* @return
* @throws Exception
* @author
*/
public String readData() throws Exception{
//这里的readLine方法是一个柱塞是方法,一直等到独到数据为止
String msg = br.readLine();
return msg;
}
}
Param.java
package anyfo.databaseanywhere.util;
/**
* 本类主要是封装了数据库链接需要的数据
* @author
*
*/
public class Param {
private String databaseDriver;
private String url;
private String userName;
private String passWord;
public String getDatabaseDriver() {
return databaseDriver;
}
public void setDatabaseDriver(String databaseDriver) {
this.databaseDriver = databaseDriver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
}
XMLUtil.java
package anyfo.databaseanywhere.util;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
* 本类主要实现对xml文件的解析
* @author hp
*
*/
public class XMLUtil {
/**
* 根据制定的配置文件xml构建一棵DOM数,并返回改树的根节点
* @param filePath
* @return
* @throws Exception
* @author
*/
public static Element getDOMTree(String filePath)throws Exception {
//取得DOM树
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(filePath);
Element root = doc.getDocumentElement();
return root;
}
/**
* 取得已知节点某属性的值
* @param Element
* @param String
* @return
* @author
*/
public static String getAttributeOfElement(Element e,String type){
return e.getAttribute(type);
}
/**
* 本方法只适用于每个标记中只有一个相同类型的子标记的情况,直接读取其文本值
* @param parent 父标记
* @param childType 子标记值
* @return
* @author
*/
public static String getTextValueOfElement(Element parent,String childType){
NodeList childList = parent.getElementsByTagName(childType);
Element curElement = (Element) childList.item(0);
String childValue ;
//这里主要进行处理,保证能够独到数据,如果读不到数据,则赋值为空
try{
childValue = curElement.getFirstChild().getNodeValue().toString();
}catch(NullPointerException n){
childValue = "";
}
return childValue;
}
}
JDBC事务 Blob Clob
一系列操作的集合称为事务(transaction);
事务的特性
原子性
一致性
隔离性
持续性
事务的分类:嵌套事务,平面事务(事务是平行的)
事务的操作(利用Connection的方法):
事务开启Conn.setAutoCommit(fasle).这里是设了一个标志
事务提交:把临时数据区的数据移送到硬盘中 conn.commit( )
回滚事务:清空临时数据区的数据,即把这次操作取消conn.rollback( );
对于JDBC默认是自动提交事务,要想自己控制事务必须把这个改为手动控制事务的方式。Conn.setAutoCommit(fasle).
对JDBC第一次启动增删改的操作的事务开始启动事务,一直到commit为止,一个事务可以包括多个增删改操作。当执行commit后表示事务结束了,然后可以进行第二个事务,这也说明了JDBC是支持平面事务的,而不是嵌套事务。
Transaction.java
package TransactionDemo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Transaction {
public static void main(String[] args) {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
//动态加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//建立连接
conn= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/zheng","root","");
System.out.println("您已经成功连接数据库!");
//设定数据库的事务操作为手动模式,JDBC默认为自动模式
conn.setAutoCommit(false);
//开始执行操作,本例子演示的是一个银行转账的功能,为了
//保证钱的安全性,必须要求在这过程中不发生异常,如果发生,则必须
//取消之前的所有增删改操作
//先查询待转账帐户的余额情况
String sqlCmd = "SELECT * FROM zheng.account WHERE name = 'zheng'";
stat = conn.createStatement();
rs = stat.executeQuery(sqlCmd);
//由于知道满足条件的记录只有一条,因此不用在遍历直接可以去数据
boolean result = rs.next();
//下面的异常是一种主动的程序退出的方法
if (result == false){
throw new Exception("您要转账的帐户不存在!");
}
//如果帐户正常则开始进行处理
//得到当前帐户的余额
int balance = rs.getInt("balance");
balance -= 10000 ;
if(balance<=0){
throw new Exception("您要转账的帐户余额不够!");
}
sqlCmd = "UPDATE account SET balance = "+balance+ " WHERE name = 'zheng'";
stat.executeUpdate(sqlCmd);
//先查询接收帐户的余额情况
sqlCmd = "SELECT * FROM zheng.account WHERE name = 'zhu'";
stat = conn.createStatement();
rs = stat.executeQuery(sqlCmd);
//由于知道满足条件的记录只有一条,因此不用在遍历直接可以去数据
result = rs.next();
//下面的异常是一种主动的程序退出的方法
if (result == false){
throw new Exception("您要存款的帐户不存在!");
}
//如果帐户正常则开始进行处理
//得到当前帐户的余额
balance = rs.getInt("balance");
balance += 10000 ;
sqlCmd = "UPDATE account SET balance = "+balance+ " WHERE name = 'zhu'";
stat.executeUpdate(sqlCmd);
//如果正常的话则提交事务
conn.commit();
//下面可以写其他的事务了。。。。。
} catch (Exception e) {
// TODO Auto-generated catch block
try {
//如果发生异常的话,则回滚事务,取消之前执行的增删改操作
conn.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
e.printStackTrace();
}
}
}
Blob(二进制) Clob(文本文件)(orcale)(在mysql里面叫text类型)大容量数据存取
(图片,压缩文件(二进制),等等)
微软的word是十进制和二进制的结合,而pdf是二进制
这个可以作为文件上传下载工具,目前解决了(硬盘<—> 数据库)上传下载
由于数据是比较大的,不可能一次传完,因此这里利用了输入输出流的概念,由系统自动传送
执行操作逻辑是:
上传:
采用带参数的sql语句
将要上传的文件绑定到一个输出流
用preparedStatement的set某方法(区分文本和二进制)设定这个参数,而这个参数要绑定到上面的建立的输出流
执行excuteupdate()
BlobDemo.java
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class BlobDemo {
//把硬盘上的一个文件上传到数据库中
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstat = null;
FileInputStream fis = null;
try {
//动态加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//建立连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/zheng","root","");
System.out.println("您已经成功连接数据库!");
//开始上传文件,使用带参数的sql语句
String sqlCmd = "Update zheng.account Set pic = ? Where name = 'zheng' ";
pstat = conn.prepareStatement(sqlCmd);
//因为文件比较大,因此需要用输入流来进行操作,为此先建立一个输出流
File file = new File(".\\BloBCLoBDemo\\t.jpg");
//通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。
fis = new FileInputStream(file);
//设定sql语句中的参数
//但是这里了要注意下面的这个方法的,第一setBinaryStream表示对于的是二进制数据
//第二:其中的第一个参数表示是参数index,第二个参数是输入流,第三个数是要传入的长度
//但是这个长度一定是int类型的,其他类型的目前不支持;
pstat.setBinaryStream(1, fis, (int)file.length());
//开始上传
pstat.executeUpdate();
//
System.out.println("您已经成功上传到数据库!");
}catch(Exception e){
e.getMessage();
}finally{
try {
fis.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
下载
里面conn的执行查询后得到一个resultset对象,
利用该对象的get某方法(区分文本和二进制)可以得到一个输出流
建立一个面向硬盘文件的输入流
实现则两个输入流和输出流之间的数据处理
BlobDownDemo.java
import java.io.FileOutputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class BlobDownDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
InputStream is = null;
FileOutputStream fos = null;
try {
//动态加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//建立连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/zheng","root","");
System.out.println("您已经成功连接数据库!");
//开始下载数据
//为此先要查询数据
String sqlCmd = "Select pic from zheng.account where name = 'zheng' ";
stat = conn.createStatement();
rs = stat.executeQuery(sqlCmd);
//将数据库中读取到的结果以输出流的形式输出
boolean result = rs.next();
if (result == false){
throw new Exception("不存在数据");
}
is = rs.getBinaryStream(1);
//建立要输出的文件,
//文件输出流FileOutputStream是用于将数据写入 File 或 FileDescriptor 的输出流。
fos = new FileOutputStream("./zheng.jpg");
//在两个流之间进行数据交换
byte [] buffer = new byte[1024];
while(true){
//读数据
int length = is.read(buffer);
if(length == -1){
break;
}
//写数据
fos.write(buffer, 0, length);
}
System.out.println("数据已经成功下载!");
}catch(Exception e){
e.printStackTrace();
}finally{
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
连接池
功能
1加快程序获取连接的速度
2 控制连接总数
3 连接的循环使用机制
4 获取连接的等待机制
5 连接数量的优化
编写逻辑:
要用创建连接池的方法(要用预建立一定数量连接的实例,以加快程序获得连接的速度,差异比较大)
要用判断能否创建的方法
要用归还的方法(用户能自己归还,也能直接close,实际也是归还)
能够适用多个不同的数据库(用配置文件读入,并加一个外壳)
保证线程安全(使用sychronized)
能够避免用户误操作(自己定义一个实现conntion的子类,用户实际上是获得该子类)
有后台守护线程做线程池的清理工作(实现连接数量的优化,并控制连接池的总数)
第一节课:B/S四层结构的介绍,引入对数据库的分析,为什么要用到JDBC。
表现层:结果展示,数据录入,触发请求
控制层:接收请求,下发数据
业务逻辑:组织业务逻辑,分配任务
数据持久:数据操作
一般数据库的文件只要数据库厂商才知道怎么去操作的,因此开发了数据库服务器,并提供了数据库客户端,用户只需在客户端上操作就直接能操作底层的数据了。但是这一个客户端每个公司的不同,造成了应用的困难,因此需要用一个统一的规范,这就是JDBC的出现。
JDBC规范
所谓规范实际上一套套的接口规定
JDBC只是定义了一套操作数据库的接口,不同数据库公司按照这个接口规范实现了相应的类,因此对于客户来说只要学习JDBC的规范就可以操作不同的数据库了,不同数据库公司实现的JDBC的接口的类库称为该数据库的驱动。
JDBC使用流程
获取数据库连接(一次一般只能连接一个库,对orcale来说只要服务的概念,也就是一个库)
服务器IP,端口(MySQL为3306)
协议(jdbc:mysql)
库或服务名称
执行SQL语句
如果执行的是查询的话,要接收查询结果
断开与数据库的连接
Class.forName(“com.mysql.jdbc.Driver”) //动态引入数据库驱动类 ,而不是直接静态的采用import方法,以此提高程序的可维护性,因为这个名字可以通过xml配置文件读入而得到。
JDBC详细应用
类介绍:
DriverManager 其中的getConnection方法可以得到一个Connection 变量
Connection 其中的createStatement()方法可以得到一个Statement变量
Statement 其中的executeUpdate()可以执行一条SQL语句,该返回值表示影响到了几条记录。executeQuery()进行查询,返回一个ResultSet对象
ResultSet: 其对象一定要关闭,当执行查询完成后,rs指向标题,故在使用时首先要用next方法,移到下一条,并可以通过返回值判断是否结束。之后就可以通过各个get方法得到不同字段的值了。
在引用这些类时,建议使用java.jdbc下面的接口,以提高程序的可移植性,如果用了某个具体数据库的实现类程序可以实现同样的效果但是可移植性不好,要换数据库时需要改动大量的源码!
//这里引用的都是sql的接口类,而不是用mySQL的实现类,主要是为了提高本程序的可移植性,用到了多态!
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
Connection conn = null;
Statement stat = null;
ResultSet set = null;
try {
//通过反射机制动态的引入应用的数据库的驱动
Class.forName("com.mysql.jdbc.Driver");
//建立连接
//先初始化连接参数mysql:jdbc为协议
String url = "jdbc:mysql://127.0.0.1:3306/storagemis";
//所连接的数据库的用户名和密码
String userName = "root";
String passWord = "";
conn = DriverManager.getConnection(url,userName,passWord);
//System.out.println(CreateID.createID());
//利用SQL语句进行数据库操作
//数据插入操作
String sqlStatement = "INSERT INTO storagemis.goods VALUES ('House',"+CreateID.createID()+",560000)";
//上面的sql语句拼接容易出错,因此可以考虑采用其他的方式。下面改进
//创建表
//String sqlStatement = "create table storagemis.test ( ID varchar(5))";
//得到一个jdbc执行语句
stat = conn.createStatement();
//执行该语句
int result = stat.executeUpdate(sqlStatement);
System.out.println("The operation affect "+result+"record");
//数据库查询操作实例
String sqlQuery = "SELECT * FROM storagemis.goods";
//执行查询操作以后返回一个ResultSet的对象
set = stat.executeQuery(sqlQuery);
//遍历查询结果
while(true){
//在最初执行查询后,指针指向标题,为了能够获取数据,一开始应该移动
//指针到第一条记录,其返回结果用来判断是否结束
boolean resultQuery = set.next();
if(resultQuery == false){
break;
}
//通过set的不同方法的表中的值
int ID = set.getInt(2);
//可以通过字段名得到
String goodsName = set.getString("goodsname");
//可以通过字段的编号得到,注意编号从0开始
int goodsPrice = set.getInt(3);
System.out.println("ID:"+ID+"-goodsName is :"+goodsName+" - goodsPrice is: "+goodsPrice);
}
//以下实例是带参数的SQL查询
String sqlParm = "update storagemis.goods set goodsname ='telphone' where goodsID = ?;";
// 通过connection的prepareStatement()方法建立一个PreparedStatement类(这是statement的子类)的对象,这表示预备sql
PreparedStatement pstatement = conn.prepareStatement(sqlParm);
//进行参数传递,下面的语句表示为预备参数的第一个?传递一个“1”参数
//这里用的setlong,如果是传递的字符串的话,就用setString()
pstatement.setLong(1,2);
//执行sql语句,但是千万注意不要再带参数,否则将出现执行错误,因为sal语句中有?,而不带参数的
//的execute则表示对PreparedStatement的对象执行。
pstatement.execute();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//注意一定要关闭ResultSet
try {
set.close();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//关闭数据库连接
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
JDBC 带参数的SQL JDBC元数据
元数据 Meta Data
本身固有的特性称为元数据
元数据的分类:
数据库的元数据:数据库的名称,版本
查询结果的元数据:字段
通过DatabasedMetaData类的方法得到数据库的元数据,该类的变量有connection.getMetaData( )方法
通过ResultSet类的getMtaData()方法得到一个ResultSetMetaData类的实例,再功过该实例的不同方法得到查询结果的元数据。
练习:双击运行,让用户输入有关数据库的信息,数据库驱动,用户名,密码,数据库。对于查询结果能够以表格的方式表现。 并进行程序的实施!
GetMetaDate.java
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
public class GetMetaDate {
/**
*
* 本实例主要是为了演示获的元数据
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
//建立链接
String databaseDriver = "com.mysql.jdbc.Driver";
//利用反射机制动态加载驱动
Class.forName(databaseDriver);
String url = "jdbc:mysql://127.0.0.1:3306/storagemis";
String userName ="root";
String passWord = "";
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
ResultSetMetaData rsMetaData = null;
//建立链接
try{
conn = DriverManager.getConnection(url,userName,passWord);
//建立好连接后就可以获得有关数据库本身的元数据信息
DatabaseMetaData dbmd = conn.getMetaData();
//从而可以根据DatabaseMetaDate得到关于数据库的各元个信息
String DatabaseProductName = dbmd.getDatabaseProductName();
String DatabaseProductVersion = dbmd.getDatabaseProductVersion();
String DriverName = dbmd.getDriverName();
System.out.println("DatabaseProductName is :"+DatabaseProductName);
System.out.println("DatabaseProductVersion is :"+DatabaseProductVersion);
System.out.println("DriverName is :"+DriverName);
//建立语句,获得查询结果的个数据元信息
String sql = "SELECT * FROM storagemis.goods";
//利用connection得打一个statement
stat = conn.createStatement();
rs = stat.executeQuery(sql);
//获得查询结果的元数据,比如行数,列数,字段名
rsMetaData = rs.getMetaData();
//再通过rsMetaData得到具体的元数据
//得到该结果集中的列数
int columnCount= rsMetaData.getColumnCount();
//在通过该列数得到每一列的名字和大小
for(int i = 1; i <= columnCount ; i ++){
String columnName = rsMetaData.getColumnName(i);
int columnSize = rsMetaData.getColumnDisplaySize(i);
System.out.println("the size of column "+ columnName+ "is "+ columnSize);
}
}catch(Exception e){
e.printStackTrace();
}finally{
//关闭各个连接
try{
conn.close();
rs.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
DBAnywhere源码:
Main.java
package anyfo.databaseanywhere.src;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Element;
import anyfo.databaseanywhere.util.Channel;
import anyfo.databaseanywhere.util.Param;
import anyfo.databaseanywhere.util.XMLUtil;
public class Main {
private int [] lengthOfField = null;
private List<String []> contentList = new ArrayList<String[]>();
/**
* 解析配置文件,得到数据库链接所需要的参数
* @param filePath
* @return
* @throws Exception
* @author
*/
public Param parseConfig(String filePath) throws Exception{
//根据配置文件路径得到一棵DOM树的根节点
Element root = (Element)XMLUtil.getDOMTree(filePath);
//利用该根节点得到各个子节点的值
String databaseDriver = XMLUtil.getTextValueOfElement(root, "databaseDriver");
String url = XMLUtil.getTextValueOfElement(root, "url");
String userName = XMLUtil.getTextValueOfElement(root, "userName");
String passWord = XMLUtil.getTextValueOfElement(root, "passWord");
Param param = new Param();
param.setDatabaseDriver(databaseDriver);
param.setPassWord(passWord);
param.setUrl(url);
param.setUserName(userName);
return param;
}
/**
* 实现对数据库的链接的建立
* @param param
* @return
* @throws Exception
* @author
*/
public Connection connectDatabase(Param param) throws Exception{
Connection conn = null;
//利用反射机制动态加载数据库的驱动程序
Class.forName(param.getDatabaseDriver());
System.out.println("数据库库正在链接中,请稍等......");
//建立链接
conn = DriverManager.getConnection(param.getUrl(),param.getUserName(), param.getPassWord());
//建立好连接后就可以获得有关数据库本身的元数据信息
System.out.println("您已成功链接到数据库");
DatabaseMetaData dbmd = conn.getMetaData();
//从而可以根据DatabaseMetaDate得到关于数据库的各元个信息
String DatabaseProductName = dbmd.getDatabaseProductName();
String DatabaseProductVersion = dbmd.getDatabaseProductVersion();
String DriverName = dbmd.getDriverName();
System.out.println("您使用的数据库库信息如下:");
//打印所链接的数据库的相关信息
System.out.println("DatabaseProductName is :"+DatabaseProductName);
System.out.println("DatabaseProductVersion is :"+DatabaseProductVersion);
System.out.println("DriverName is :"+DriverName);
return conn;
}
/**
* 实现对更新类操作的处理
* @param conn
* @param cmd
* @throws Exception
* @author
*/
public void update(Connection conn,String cmd)throws Exception {
Statement stat = null;
try{
stat = conn.createStatement();
int result = stat.executeUpdate(cmd);
System.out.println("您的操作已经成功,并且影响到了 "+result+"记录");
}catch(SQLException s){
//打印出失败信息!
System.out.println("您的操作失败"+s.getMessage());
}finally{
stat.close();
}
}
/**
* 数据扫描功能,实现对数据库的内容本地保存
* @param conn 链接
* @param cmd 命令
* @throws Exception
* @author
*/
public void scan(Connection conn,String cmd) throws Exception{
Statement stat = null;
ResultSet rs = null;
ResultSetMetaData rsMetaData = null;
//得到一个jdbc执行语句
stat = conn.createStatement();
//获得查询结果集
rs = stat.executeQuery(cmd);
//获得查询结果的元数据,比如行数,列数,字段名
rsMetaData = rs.getMetaData();
//再通过rsMetaData得到具体的元数据
//得到该结果集中的列数
int columnCount= rsMetaData.getColumnCount();
//创建数组
this.lengthOfField = new int[columnCount];
String [] contentArray = new String[columnCount];
//初始化该长度数组,用各表的字段名字的长度作为初始值,同时把各字段值保留到字符数组中
for(int i = 1 ; i <= columnCount ; i++){
String contentValue = rsMetaData.getColumnName(i);
int size = contentValue.length();
this.lengthOfField[i -1 ] = size;
contentArray[i -1 ] = contentValue;
}
this.contentList.add(contentArray);
//更新长度数组,把具体值中较大的长度放入到长度数组中,并同时把值保留到列表中。
//遍历查询结果
while(true){
//在最初执行查询后,指针指向标题,为了能够获取数据,一开始应该移动
//指针到第一条记录,其返回结果用来判断是否结束
boolean resultQuery = rs.next();
if(resultQuery == false){
break;
}
String [] dataArray = new String[columnCount];
for(int i = 1 ; i <= columnCount ; i++){
String contentValue = rs.getString(i);
int size = contentValue.length();
int max = Math.max( size, this.lengthOfField[ i - 1 ] );
this.lengthOfField[ i - 1 ] = max;
dataArray[i -1 ] = contentValue;
}
this.contentList.add(dataArray);
}
}
/**
* 主要实现格式化显示数据的功能
* @author
*/
public void displayData( ){
int columnCount = this.lengthOfField.length;
for(String [] content:this.contentList){
//该循环打印表头的横线
for(int i = 0 ; i < columnCount ; i ++){
//columnCount决定打印多少个表格框
System.out.print("|");
for(int j = 0 ; j < this.lengthOfField[i];j++){
//这层循环决定打印每个框中的东西
System.out.print("-");
}
}
System.out.print("|\n");
//目前状态已经换行,可以开始打印数据库的内容,需要从contentList取出一个数组来进行打印
int index = 0;
for ( String columnValue: content ) {
System.out.print( "|" );
System.out.print( columnValue );
int size = this.lengthOfField[ index ];
for ( int i = 0 ; i < size - columnValue.length() ; i ++ ) {
//打印剩余的空格
System.out.print( " " );
}
index ++;
}
System.out.print( "|\n" );
}
//打印表尾的横线
for(int i = 0 ; i < columnCount ; i ++){
//columnCount决定打印多少个表格框
System.out.print("|");
for(int j = 0 ; j < this.lengthOfField[i];j++){
//这层循环决定打印每个框中的横线
System.out.print("-");
}
}
System.out.print("|\n");
}
/**
* 查询处理函数,主要完成逻辑流程管理,即:扫描数据库、格式化显示数据
* @param conn
* @param cmd
* @throws Exception
* @author
*/
public void queryPrcocess(Connection conn,String cmd)throws Exception {
//查询操作处理的关键是打印表格
//为了实现这个目的先把所有的数据保存下来,这是一个非常好的思路,把数据库的东西保留到内存中去
//以后处理的时候只需要从内存中读取数据就可以了
//扫描数据,并保留数据,其格式是长度数组保留没列中最大的长度,而内容列表中保留一条记录(其中记录
//用String数组保存
//扫描查询结果,并保留到类的成员变量
this.scan(conn,cmd);
//下面的代码开始按格式显示数据
this.displayData();
//一次查询完成后要清空list中的数据;
this.contentList.clear();
}
/**
* DBanywher的启动方法,主要是实现对输入命令的判断,从而选择不同的执行方法
* @param param
* @throws Exception
* @author
*/
public void start(Param param) throws Exception{
//根据各个参数建立链接,并返回链接建立链接
Connection conn = null;
Channel chan = null;
try{
conn = this.connectDatabase(param);
//等待用户读入数据并进行处理
chan = Channel.getChannel();
//一直等待用户的输入
while(true){
System.out.println("请输入你要对该数据库表进行操作的指令(Exit退出)");
String cmd = chan.readData();
//判断用户是否输入exit以便退出,故要求进行判断,为此先把它转换为小写
cmd.toLowerCase();
if(cmd.equals("exit")==true){
break;
//表示退出系统,否则进行下面的处理
}
//下面要根据输入的命令类型进行不同处理,主要是分为两类,查询和更新操作(包括修改,删除,增加)
//为此要对命令进行区分,可以判断命令的前几个字符来判断
if(cmd.startsWith("select")){
//表示进行查询操作
this.queryPrcocess(conn, cmd);
}else{
//表示进行更新操作
this.update(conn, cmd);
}
}
}catch(Exception e){
e.printStackTrace();
}finally{
chan.closeChannel();
conn.close();
}
}
/**
* 主函数,启动程序
* @param args
* @throws Exception
*
*/
public static void main(String[] args)throws Exception {
// TODO Auto-generated method stub
// 读取配置文件,保存各个参数
Main m = new Main();
//运行此带参数的程序需要选用open run dialog,并输入参数
Param param = m.parseConfig(args[0]);
//开始进行处理
m.start(param);
}
}
Channel.java
package anyfo.databaseanywhere.util;
/**
* 本类主要是实现对数据从显示器上读取的管道的建立
*
*/
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Channel {
private InputStream is;
private InputStreamReader isr;
private BufferedReader br;
//做成一个单利模式
private static Channel chan;
//为实现单例,其构造函数必须是私有的
/**
* 私有化构造器,以实现单例模式
* @author
*/
private Channel(){
this.buildChannel();
}
/**
* 由于构造函数是私有的,因此需要为客户提供一个构建该实例的方法
* @return
* @author
*/
public static Channel getChannel(){
if(Channel.chan == null){
Channel.chan = new Channel();
}
return Channel.chan;
}
/**
* 建立管道,不过这是一个私有方法
* @author
*/
private void buildChannel(){
is = System.in;
isr = new InputStreamReader(is);
br = new BufferedReader(isr);
}
/**
* 关闭管道
* @throws Exception
* @author
*/
public void closeChannel() throws Exception{
is.close();
isr.close();
br.close();
}
/**
* 读数据,这是一个柱塞是方法
* @return
* @throws Exception
* @author
*/
public String readData() throws Exception{
//这里的readLine方法是一个柱塞是方法,一直等到独到数据为止
String msg = br.readLine();
return msg;
}
}
Param.java
package anyfo.databaseanywhere.util;
/**
* 本类主要是封装了数据库链接需要的数据
* @author
*
*/
public class Param {
private String databaseDriver;
private String url;
private String userName;
private String passWord;
public String getDatabaseDriver() {
return databaseDriver;
}
public void setDatabaseDriver(String databaseDriver) {
this.databaseDriver = databaseDriver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
}
XMLUtil.java
package anyfo.databaseanywhere.util;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
* 本类主要实现对xml文件的解析
* @author hp
*
*/
public class XMLUtil {
/**
* 根据制定的配置文件xml构建一棵DOM数,并返回改树的根节点
* @param filePath
* @return
* @throws Exception
* @author
*/
public static Element getDOMTree(String filePath)throws Exception {
//取得DOM树
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(filePath);
Element root = doc.getDocumentElement();
return root;
}
/**
* 取得已知节点某属性的值
* @param Element
* @param String
* @return
* @author
*/
public static String getAttributeOfElement(Element e,String type){
return e.getAttribute(type);
}
/**
* 本方法只适用于每个标记中只有一个相同类型的子标记的情况,直接读取其文本值
* @param parent 父标记
* @param childType 子标记值
* @return
* @author
*/
public static String getTextValueOfElement(Element parent,String childType){
NodeList childList = parent.getElementsByTagName(childType);
Element curElement = (Element) childList.item(0);
String childValue ;
//这里主要进行处理,保证能够独到数据,如果读不到数据,则赋值为空
try{
childValue = curElement.getFirstChild().getNodeValue().toString();
}catch(NullPointerException n){
childValue = "";
}
return childValue;
}
}
JDBC事务 Blob Clob
一系列操作的集合称为事务(transaction);
事务的特性
原子性
一致性
隔离性
持续性
事务的分类:嵌套事务,平面事务(事务是平行的)
事务的操作(利用Connection的方法):
事务开启Conn.setAutoCommit(fasle).这里是设了一个标志
事务提交:把临时数据区的数据移送到硬盘中 conn.commit( )
回滚事务:清空临时数据区的数据,即把这次操作取消conn.rollback( );
对于JDBC默认是自动提交事务,要想自己控制事务必须把这个改为手动控制事务的方式。Conn.setAutoCommit(fasle).
对JDBC第一次启动增删改的操作的事务开始启动事务,一直到commit为止,一个事务可以包括多个增删改操作。当执行commit后表示事务结束了,然后可以进行第二个事务,这也说明了JDBC是支持平面事务的,而不是嵌套事务。
Transaction.java
package TransactionDemo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Transaction {
public static void main(String[] args) {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
//动态加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//建立连接
conn= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/zheng","root","");
System.out.println("您已经成功连接数据库!");
//设定数据库的事务操作为手动模式,JDBC默认为自动模式
conn.setAutoCommit(false);
//开始执行操作,本例子演示的是一个银行转账的功能,为了
//保证钱的安全性,必须要求在这过程中不发生异常,如果发生,则必须
//取消之前的所有增删改操作
//先查询待转账帐户的余额情况
String sqlCmd = "SELECT * FROM zheng.account WHERE name = 'zheng'";
stat = conn.createStatement();
rs = stat.executeQuery(sqlCmd);
//由于知道满足条件的记录只有一条,因此不用在遍历直接可以去数据
boolean result = rs.next();
//下面的异常是一种主动的程序退出的方法
if (result == false){
throw new Exception("您要转账的帐户不存在!");
}
//如果帐户正常则开始进行处理
//得到当前帐户的余额
int balance = rs.getInt("balance");
balance -= 10000 ;
if(balance<=0){
throw new Exception("您要转账的帐户余额不够!");
}
sqlCmd = "UPDATE account SET balance = "+balance+ " WHERE name = 'zheng'";
stat.executeUpdate(sqlCmd);
//先查询接收帐户的余额情况
sqlCmd = "SELECT * FROM zheng.account WHERE name = 'zhu'";
stat = conn.createStatement();
rs = stat.executeQuery(sqlCmd);
//由于知道满足条件的记录只有一条,因此不用在遍历直接可以去数据
result = rs.next();
//下面的异常是一种主动的程序退出的方法
if (result == false){
throw new Exception("您要存款的帐户不存在!");
}
//如果帐户正常则开始进行处理
//得到当前帐户的余额
balance = rs.getInt("balance");
balance += 10000 ;
sqlCmd = "UPDATE account SET balance = "+balance+ " WHERE name = 'zhu'";
stat.executeUpdate(sqlCmd);
//如果正常的话则提交事务
conn.commit();
//下面可以写其他的事务了。。。。。
} catch (Exception e) {
// TODO Auto-generated catch block
try {
//如果发生异常的话,则回滚事务,取消之前执行的增删改操作
conn.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
e.printStackTrace();
}
}
}
Blob(二进制) Clob(文本文件)(orcale)(在mysql里面叫text类型)大容量数据存取
(图片,压缩文件(二进制),等等)
微软的word是十进制和二进制的结合,而pdf是二进制
这个可以作为文件上传下载工具,目前解决了(硬盘<—> 数据库)上传下载
由于数据是比较大的,不可能一次传完,因此这里利用了输入输出流的概念,由系统自动传送
执行操作逻辑是:
上传:
采用带参数的sql语句
将要上传的文件绑定到一个输出流
用preparedStatement的set某方法(区分文本和二进制)设定这个参数,而这个参数要绑定到上面的建立的输出流
执行excuteupdate()
BlobDemo.java
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class BlobDemo {
//把硬盘上的一个文件上传到数据库中
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstat = null;
FileInputStream fis = null;
try {
//动态加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//建立连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/zheng","root","");
System.out.println("您已经成功连接数据库!");
//开始上传文件,使用带参数的sql语句
String sqlCmd = "Update zheng.account Set pic = ? Where name = 'zheng' ";
pstat = conn.prepareStatement(sqlCmd);
//因为文件比较大,因此需要用输入流来进行操作,为此先建立一个输出流
File file = new File(".\\BloBCLoBDemo\\t.jpg");
//通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。
fis = new FileInputStream(file);
//设定sql语句中的参数
//但是这里了要注意下面的这个方法的,第一setBinaryStream表示对于的是二进制数据
//第二:其中的第一个参数表示是参数index,第二个参数是输入流,第三个数是要传入的长度
//但是这个长度一定是int类型的,其他类型的目前不支持;
pstat.setBinaryStream(1, fis, (int)file.length());
//开始上传
pstat.executeUpdate();
//
System.out.println("您已经成功上传到数据库!");
}catch(Exception e){
e.getMessage();
}finally{
try {
fis.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
下载
里面conn的执行查询后得到一个resultset对象,
利用该对象的get某方法(区分文本和二进制)可以得到一个输出流
建立一个面向硬盘文件的输入流
实现则两个输入流和输出流之间的数据处理
BlobDownDemo.java
import java.io.FileOutputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class BlobDownDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
InputStream is = null;
FileOutputStream fos = null;
try {
//动态加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//建立连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/zheng","root","");
System.out.println("您已经成功连接数据库!");
//开始下载数据
//为此先要查询数据
String sqlCmd = "Select pic from zheng.account where name = 'zheng' ";
stat = conn.createStatement();
rs = stat.executeQuery(sqlCmd);
//将数据库中读取到的结果以输出流的形式输出
boolean result = rs.next();
if (result == false){
throw new Exception("不存在数据");
}
is = rs.getBinaryStream(1);
//建立要输出的文件,
//文件输出流FileOutputStream是用于将数据写入 File 或 FileDescriptor 的输出流。
fos = new FileOutputStream("./zheng.jpg");
//在两个流之间进行数据交换
byte [] buffer = new byte[1024];
while(true){
//读数据
int length = is.read(buffer);
if(length == -1){
break;
}
//写数据
fos.write(buffer, 0, length);
}
System.out.println("数据已经成功下载!");
}catch(Exception e){
e.printStackTrace();
}finally{
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
连接池
功能
1加快程序获取连接的速度
2 控制连接总数
3 连接的循环使用机制
4 获取连接的等待机制
5 连接数量的优化
编写逻辑:
要用创建连接池的方法(要用预建立一定数量连接的实例,以加快程序获得连接的速度,差异比较大)
要用判断能否创建的方法
要用归还的方法(用户能自己归还,也能直接close,实际也是归还)
能够适用多个不同的数据库(用配置文件读入,并加一个外壳)
保证线程安全(使用sychronized)
能够避免用户误操作(自己定义一个实现conntion的子类,用户实际上是获得该子类)
有后台守护线程做线程池的清理工作(实现连接数量的优化,并控制连接池的总数)