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方法