今天我们来说一下关于JDBC的相关知识,关于JDBC我想大家都不陌生了,而且我记得早就开始使用它了,记得那是大二的时候做课程设计,但是那时候是为了完成任务,所以遇到问题就google,那时候也没有时间去整理,所以这次就来详细说一下关于JDBC的知识
摘要:
JDBC(Java Data Base Connectivity,java数据库连接),由一些接口和类构成的API。
J2SE的一部分,由java.sql,javax.sql包组成。
应用程序、JDBC API、数据库驱动及数据库之间的关系
JDBC的使用步骤
1.注册驱动 (只做一次)
方式一:Class.forName(“com.mysql.jdbc.Driver”);
推荐这种方式,不会对具体的驱动类产生依赖。
方式二:DriverManager.registerDriver(com.mysql.jdbc.Driver);
会造成DriverManager中产生两个一样的驱动,并会对具体的驱动类产生依赖。
方式三:System.setProperty(“jdbc.drivers”, “driver1:driver2”);
虽然不会对具体的驱动类产生依赖;但注册不太方便,所以很少使用。
驱动类型(四种类型)
2.建立连接(Connection)
[java] view plain copy
在CODE上查看代码片派生到我的代码片
Connection conn = DriverManager.getConnection(url, user, password);
url格式:
JDBC:子协议:子名称//主机名:端口/数据库名?属性名=属性值&…
User,password可以用“属性名=属性值”方式告诉数据库;
其他参数如:useUnicode=true&characterEncoding=GBK。
3.创建执行SQL的语句(Statement)
[java] view plain copy
在CODE上查看代码片派生到我的代码片
Statement
Statement st = conn.createStatement();
st.executeQuery(sql);
PreparedStatement
String sql = “select * from table_name where col_name=?”;
PreparedStatement ps = conn.preparedStatement(sql);
ps.setString(1, “col_value”);
ps.executeQuery();
4.处理执行结果(ResultSet)
[java] view plain copy
在CODE上查看代码片派生到我的代码片
ResultSet rs = statement.executeQuery(sql);
While(rs.next()){
rs.getString(“col_name”);
rs.getInt(“col_name”);
//…
}
5.释放资源
释放ResultSet, Statement,Connection.
数据库连接(Connection)是非常稀有的资源,用完后必须马上释放,如果Connection不能及时正确的关闭将导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
下面来看一下完整的Demo:
工具类:JdbcUtils
[java] view plain copy
在CODE上查看代码片派生到我的代码片
package com.weijia.firstdemo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
public class JdbcUtils {
private static String user = "root";
private static String password = "123456";
private static String dbName = "test";
private static String url = "jdbc:mysql://localhost:3306/"+dbName+"?user="+user+"&password="+password+"&useUnicode=true&characterEncoding=gb2312";
private static DataSource dataSource = null;
/**
* 加载驱动
*/
static{
try{
Class.forName("com.mysql.jdbc.Driver");
}catch(Exception e){
System.out.println("Exception:"+e.getMessage()+"");
throw new ExceptionInInitializerError(e);
}
}
private JdbcUtils(){
}
/**
* 获取连接
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException{
return DriverManager.getConnection(url);
}
public static DataSource getDataSource(){
return dataSource;
}
/**
* 释放资源
* @param rs
* @param st
* @param conn
*/
public static void free(ResultSet rs,Statement st,Connection conn){
try{
if(rs != null){
rs.close();
}
}catch(SQLException e){
e.printStackTrace();
}finally{
try{
if(st != null){
st.close();
}
}catch(SQLException e){
e.printStackTrace();
}finally{
try{
if(conn != null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
测试类:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
package com.weijia.firstdemo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo {
public static void main(String[] args) throws Exception{
//测试代码:
test();
//标准规范代码:
template();
}
//模板代码
public static void template(){
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
//创建语句
st = conn.createStatement();
//执行语句
rs = st.executeQuery("select * from user");
//处理结果
while(rs.next()){
System.out.println(rs.getObject(1) + "\t" + rs.getObject(2) + "\t" + rs.getObject(3) + "\t");
}
}catch(SQLException e){
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.free(rs, st, conn);
}
}
//测试
static void test() throws Exception{
//注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//通过系统属性来注册驱动
System.setProperty("jdbc.drivers","");
//静态加载驱动
Class.forName("com.mysql.jdbc.Driver");
//建立连接
String url = "jdbc:mysql://localhost:3306";
String userName = "root";
String password = "";
Connection conn = DriverManager.getConnection(url,userName,password);
//创建语句
Statement st = conn.createStatement();
//执行语句
ResultSet rs = st.executeQuery("select * from user");
//处理结果
while(rs.next()){
System.out.println(rs.getObject(1) + "\t" + rs.getObject(2) + "\t" + rs.getObject(3) + "\t");
}
//释放资源
rs.close();
st.close();
conn.close();
}
}
注意:这里还要记住引入额外的jar.这个网上很多的,这里使用的是MySql,搜一下MySql驱动的jar就行了。这里我们将一些操作都放到一个工具类中,这种方式是很优雅的。
使用JDBC来实现CRUD的操作
我们这里就采用分层操作:Dao层,Service层
首先看一下domain域中的User实体
[java] view plain copy
在CODE上查看代码片派生到我的代码片
package com.weijia.domain;
import java.util.Date;
public class User {
private int id;
private String name;
private Date birthday;
private float money;
public User(){
}
public User(int id,String name,Date birthday,float money){
this.id = id;
this.name = name;
this.birthday = birthday;
this.money = money;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public float getMoney() {
return money;
}
public void setMoney(float money) {
this.money = money;
}
@Override
public String toString(){
return "[id="+id+",name="+name+",birthday="+birthday+",money="+money+"]";
}
}
再来看一下Dao层结构:
接口:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
package com.weijia.domain;
public interface UserDao {
//添加用户
public void addUser(User user);
//通过userid查询用户,id是唯一的,所以返回的是一个user
public User getUserById(int userId);
//更新用户信息
public int update(User user);
//删除用户信息
public int delete(User user);
}
实现类:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
package com.weijia.domain;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import com.weijia.firstdemo.JdbcUtils;
public class UserDaoImpl implements UserDao{
/**
* 添加用户
*/
public void addUser(User user) {
Connection conn = null;
PreparedStatement st = null;
try{
conn = JdbcUtils.getConnection();
String sql = "insert into user(id,name,birthday,money) values(?,?,?,?)";
st = conn.prepareStatement(sql);
st.setInt(1,user.getId());
st.setString(2,user.getName());
//日期格式的转换(utils.date转化成sql.date)
st.setDate(3,new Date(user.getBirthday().getTime()));
st.setFloat(4, user.getMoney());
int count = st.executeUpdate();
System.out.println("添加记录条数:"+count);
}catch(Exception e){
throw new DaoException(e.getMessage(),e);
}finally{
JdbcUtils.free(null, st, conn);
}
}
/**
* 删除用户
*/
public int delete(User user) {
Connection conn = null;
PreparedStatement st = null;
try{
conn = JdbcUtils.getConnection();
String sql = "delete from user where id=?";
st = conn.prepareStatement(sql);
st.setInt(1,user.getId());
int count = -1;
count = st.executeUpdate();
System.out.println("删除记录条数:"+count);
return count;
}catch(Exception e){
throw new DaoException(e.getMessage(),e);
}finally{
JdbcUtils.free(null, st, conn);
}
}
/**
* 通过userId获取用户信息
*/
public User getUserById(int userId) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "select * from user where id=?";
st = conn.prepareStatement(sql);
st.setInt(1,userId);
rs = st.executeQuery();
if(rs.next()){
User user = new User();
user.setId(userId);
user.setName(rs.getString("name"));
user.setBirthday(rs.getDate("birthday"));
user.setMoney(rs.getFloat("money"));
return user;
}
}catch(Exception e){
throw new DaoException(e.getMessage(),e);
}finally{
JdbcUtils.free(rs, st, conn);
}
return null;
}
/**
* 更新用户信息
*/
public int update(User user){
Connection conn = null;
PreparedStatement st = null;
try{
conn = JdbcUtils.getConnection();
String sql = "update user set name=?,birthday=?,money=? where id=?";
st = conn.prepareStatement(sql);
st.setString(1,user.getName());
st.setDate(2,new Date(user.getBirthday().getTime()));
st.setFloat(3,user.getMoney());
st.setInt(3,user.getId());
int count = 0;
count = st.executeUpdate();
System.out.println("更新的记录数:"+count);
return count;
}catch(Exception e){
throw new DaoException(e.getMessage(),e);
}finally{
JdbcUtils.free(null, st, conn);
}
}
}
然后是Servic层:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
package com.weijia.domain;
public class UserService {
private UserDao userDao;
public UserService(){
//通过工厂实例化UserDao对象
userDao = DaoFactory.getInstance().createUserDao();
System.out.println("userDao:"+userDao);
}
/**
* 注册用户
* @param user
*/
public void regist(User user){
if(user == null){
System.out.println("注册信息无效!!");
}else{
userDao.addUser(user);
}
}
/**
* 查询用户
* @param userId
* @return
*/
public User query(int userId){
User user = userDao.getUserById(userId);
if(user == null){
System.out.println("查询结果为空!!");
}else{
System.out.println(user.getId()+"\t"+user.getName()+"\t"+user.getBirthday()+"\t"+user.getMoney());
}
return userDao.getUserById(userId);
}
/**
* 更新用户
* @param user
*/
public void update(User user){
if(user.getId()<=0){
System.out.println("用户id无效,无法更新");
}else{
userDao.update(user);
}
}
/**
* 删除用户
* @param user
*/
public void delete(User user){
if(user.getId()<=0){
System.out.println("用户id无效,无法删除!!");
}else{
userDao.delete(user);
}
}
}
这里我们还需要额外的两个类:
一个是异常类,因为我们需要自定义我们自己的一个异常,这样方便进行捕获:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
package com.weijia.domain;
public class DaoException extends RuntimeException{
private static final long serialVersionUID = 1L;
public DaoException(){
}
public DaoException(Exception e){
super(e);
}
public DaoException(String msg){
super(msg);
}
public DaoException(String msg,Exception e){
super(msg,e);
}
}
同时,我们这里面采用工厂模式进行实例化UserDao对象:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
package com.weijia.domain;
import java.io.FileInputStream;
import java.util.Properties;
public class DaoFactory {
/**
* 单例模式
*/
private static UserDao userDao = null;
private static DaoFactory instance = new DaoFactory();
private DaoFactory(){
/**
* 通过读取属性文件来动态的加载Dao层类
*/
Properties prop = new Properties();
try{
FileInputStream fis = new FileInputStream("src/com/weijia/domain/daoconfig.properties");
prop.load(fis);
String className = prop.getProperty("userDaoClass");
Class<?> clazz = Class.forName(className);
userDao = (UserDao)clazz.newInstance();
fis.close();
}catch(Throwable e){
throw new ExceptionInInitializerError(e);
}
}
public static DaoFactory getInstance(){
return instance;
}
public UserDao createUserDao(){
return userDao;
}
}
这里面是读取properties文件,然后去读取类名进行加载,这种方式是很灵活的
测试:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
package com.weijia.domain;
import java.util.Date;
public class TestDemo {
public static void main(String[] args) throws Exception{
UserService userService = new UserService();
System.out.println("添加用户:");
userService.regist(new User(1,"jiangwei",new Date(System.currentTimeMillis()),300));
}
}
这里我们看到其实这些操作真的很简单,就是按照那样的几个步骤来操作即可,同时我们还需要将结构进行分层,以便管理,我们这里面测试的时候,撇开了创建数据库的一个环节,至于那个环节,也是不难的,可以从网上搜索一下即可。
Statement中的sql依赖注入的问题
接着来看一下关于我们上面的例子中使用了Statement进行操作的,其实这里面是存在一个问题的,就是会有sql注入的问题,我们先来看一下这个问题:
查询学生信息:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
/**
* 使用Statement读取数据
* @param name
* @throws SQLException
*/
static void read(String name) throws SQLException{
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
//创建语句
st = conn.createStatement();
//执行语句(不建议使用*)
String sql = "select id,name from user where name='"+name+"'";
rs = st.executeQuery(sql);
//根据列名取数据
while(rs.next()){
System.out.println(rs.getObject("id") + "\t" + rs.getObject("name") + "\t");
}
}catch(SQLException e){
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.free(rs, st, conn);
}
}
我们使用代码测试一下:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
read("'or 1 or'");
我们运行会发现,将查询出所有的学生的记录,这个是什么原因呢?我们不妨将sql打印一下会发现:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
select id,name from user where name=''or 1 or''
擦,因为sql语句中把1认为是true,又因为是或的关系,所以将所有的学生的信息查询出来了,这个就是sql注入,因为Statement会把传递进来的参数进行一下转化操作,用引号包含一下,所以会出现这个问题,那么我们该怎么解决呢?有的同学说我们可以添加一句过滤的代码,将传递的参数取出单引号,这个方法是可行的的,但是这个只能解决那些使用单引号的数据库,可能有的数据库使用的是双引号包含内容,那就不行了,所以应该想一个全套的方法,那么这里我们就是用一个叫做:PreparedStatement类,这个类是Statement类的子类,关于这两个类的区别可以查看我的另外一片文章:
http://blog.csdn.net/jiangwei0910410003/article/details/26143977
我们这里只看这个sql注入的问题:
我们将上面读取用户信息的代码改写成PreparedStatement:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
/**
* 使用PreparedStatement
* @param name
* @throws SQLException
*/
static void readPrepared(String name) throws SQLException{
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
//执行语句(不建议使用*)
String sql = "select id,name from user where name=?";
//创建语句
st = conn.prepareStatement(sql);
st.setString(1, name);
rs = st.executeQuery();
//根据列名取数据
while(rs.next()){
System.out.println(rs.getObject("id") + "\t" + rs.getObject("name") + "\t");
}
}catch(Exception e){
}
}
之后我们在执行:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
readPrepared("'or 1 or'");
就不会全部查出来了,只会查询空结果,因为表中没有一个学生的名字叫做 ‘or 1 or’。
JDBC中特殊数据类型的操作问题
第一个是日期问题:
我们在操作日期问题的时候会发现,使用PreparedStatement进行参数赋值的时候,有一个方法是:setDate(…),但是这个方法接收的参数是sql中的Date类,而不是我们平常使用的util中的Date类,所以我们要做一次转化,通常我们是这样做的,就是在定义实体类的时候将其日期型的属性定义成util中的Date类型,在进行数据库操作的时候.
进行一次转换:setDate(x,new Date(birthday.getTime());,这里birthday就是一个util.Date类型的一个属性,而new Date是sql.Date类型的,这样转化就可以了,同样我们在读取数据的时候将转化操作反过来即可。
第二个问题就是大文本数据的问题
因为有时候我们会存入一些文本内容,因为varchar的大小在mysql中也是有上线的,所以我们这里要使用blob类型了,我们这里来看一下实例:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
/**
* 插入大文本
*/
static void insert(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "insert into clob_test(bit_text) values(?)";
ps = conn.prepareStatement(sql);
File file = new File("src/com/weijia/type/ClubDemo.java");
Reader reader = new BufferedReader(new FileReader(file));
//ps.setAsciiStream(1, new FileInputStream(file), (int)file.length());//英文的文档
ps.setCharacterStream(1, reader, (int)file.length());
ps.executeUpdate();
reader.close();
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.free(rs,ps,conn);
}
}
我们将一个Java代码文件插入到数据库中
我们查询一下clob_test表:
我们看到文件内容存入到库中了。同样我们也可以从表中读取一段文本出来,使用
[java] view plain copy
在CODE上查看代码片派生到我的代码片
Clob clob = rs.getClob(1);
InputStream is = clob.getAsciiStream();
或者读取一个Reader也是可以的,这里的InputStream和Reader是针对不同流,一个字节流,这个不需要关心编码问题的,Reader是字符流需要关心编码问题。
JDBC中事务的概念
我们当初在学习数据库的时候就了解事务的概念了,事务在数据库中的地位是很重要的。在JDBC中默认情况事务是自动提交的,所以我们在进行CRUD操作的时候不需要关心开启事务,提交事务,事务回滚的一些操作,那么下面我们就来看一下怎么手动的操作一些事务:
下载我们假定这样的一个场景:
有来两个用户1和2,现在
将用户1中的账户的钱减少10
查询用户2中的账户的钱,如果钱少于300,就增加10,否则抛出异常
看一下代码:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
static void test() throws Exception{
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
/**************事务START********************/
conn.setAutoCommit(false);
st = conn.createStatement();
String sql = "update user set money=money-10 where id=1";
st.executeUpdate(sql);
sql = "select money from user where id=2";
rs = st.executeQuery(sql);
float money = 0.0f;
if(rs.next()){
money = rs.getFloat("money");
}
if(money>300){
throw new RuntimeException("已经超过最大值");
}
sql = "update user set money=money+10 where id=2";
st.executeUpdate(sql);
conn.commit();
/*******************事务END*********************/
}catch(RuntimeException e){
}finally{
JdbcUtils.free(rs, st, conn);
}
}
我们运行测试一下,因为我们这里想让它抛出异常,所以我们将用户2中的钱改成大于300的,运行一下,结果抛出异常了,但是我们发现了用户1中的钱少了10,但是由于抛出异常,所以后面的代码不执行了,用户2中的钱没有变化,那么这样的操作明显不对的,所以我们这时候要解决这个问题,使用事务的回滚操作,在捕获到异常的时候需要做回滚操作:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
if(conn != null){
conn.rollback();
}
这样即使抛出了异常,这些操作也会进行回滚的,那么用户1中的钱就不会少10了。
同时上面我们看到,我们是在开始的时候手动的关闭事务的自动提交,然后再手动的提交事务,下面再来看一下事务的保存点的问题。
场景:在上面的基础上,我们添加一个用户3,同时对用户1和用户3中的钱进行减少10,用户2的操作不变,但是当抛出异常的时候,我们希望用户1的操作还是有效的,用户3的操作还原,这时候我们需要将事务回滚到用户3的那个点就可以了,这就是事务的保存点的概念,看一下代码:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
static void test() throws Exception{
Connection conn = null;
Statement st = null;
ResultSet rs = null;
Savepoint sp = null;
try{
conn = JdbcUtils.getConnection();
/**************事务START********************/
conn.setAutoCommit(false);
st = conn.createStatement();
String sql = "update user set money=money-10 where id=1";
st.executeUpdate(sql);
sp = conn.setSavepoint();//设置回滚点
sql = "update user set money=money-10 where id=3";
st.executeUpdate(sql);
sql = "select money from user where id=2";
rs = st.executeQuery(sql);
float money = 0.0f;
if(rs.next()){
money = rs.getFloat("money");
}
System.out.println("money:"+money);
if(money>300){
throw new RuntimeException("已经超过最大值");
}
sql = "update user set money=money+10 where id=2";
st.executeUpdate(sql);
conn.commit();
/*******************事务END*********************/
}catch(SQLException e){
if(conn != null && sp != null){
conn.rollback(sp);
conn.commit();
}
}finally{
JdbcUtils.free(rs, st, conn);
}
}
我们在用户1之后设置一个保存点,在异常中只需要回滚到保存点就可以了。