一、概述
java连接数据库技术,是一项由sun公司发布的用于java连接数据库的通用标准,这套标准由一系列的接口(Connection,Statement,ResultSet等)构成,旨在屏蔽不同数据库之间的差异,为多种关系型数据库提供统一访问,使得开发者无需关注不同数据库系统之间的差异,简化java连接数据库的步骤,使用前需导入不同数据库的驱动包
二、JDBC操作数据库步骤
- 加载驱动程序
- 获取数据库连接对象
- 创建一个执行列表(载体)
- 执行SQL语句(DML,DQL)
- 处理结果集
- 关闭资源
//MySQL5为例执行DML操作
public class Test {
public static void main(String[] args) throws Exception {
//加载驱动程序
//DriverManager.registerDriver(new Driver());//加载到Class列表中,不建议使用
Class.forName("com.mysql.jdbc.Driver");
//获取数据库连接对象
String url ="JDBC:mysql://localhost:3306/test";//数据库地址
Connection conn = DriverManager.getConnection(url, "用户名", "密码");
//创建一个执行列表
//PreparedStatement stmt = conn.prepareStatement(sql);//用PreparedStatement更好
Statement stmt = conn.createStatement();
//执行DML语句
//int m = stmt.executeUpdate();
int m = stmt.executeUpdate("delete from user where uid=0");
//处理结果
System.out.println(m>0?"删除成功!":"删除失败!");
//关闭资源
m.close();
stmt.close();
conn.close();
}
}
//MySQL5为例执行DQL操作
public class Test {
public static void main(String[] args) throws Exception {
//加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
//获取数据库连接对象
String url ="JDBC:mysql://localhost:3306/test";//数据库地址
Connection conn = DriverManager.getConnection(url, "用户名", "密码");
//创建一个执行列表
/*
PreparedStatement stmt = conn.prepareStatement(sql);//用PreparedStatement更好,sql语句中可写?代表位置参数(占位符)
stmt.setInt(为第几个位置参数赋值,值);//位置参数从1开始,模糊查询值为%值%
*/
Statement stmt = conn.createStatement();
//执行DQL语句
//ResultSet m = stmt.executeQuery();
int id = 1;
String name = "张三";
ResultSet rs = stmt.executeQuery("select * from employee where uid=" + id "and uname='" + name + "'");//字符串拼接,容易形成sql注入即密码为12dd34' or '1'='1时密码为真
//处理结果
while(rs.next()) {
System.out.println(rs.getInt("uid")+rs.getString("uname"));//字段名可使用数字,表示为列字段的顺序,从1开始
System.out.println("-------------");
}
//关闭资源(先开后关)
rs.close();
stmt.close();
conn.close();
}
}
- JDBC中,PreparedStatement相较于Statement有什么优点?
- PreparedStatement可以使用预编译的sql,而Statment只能使用静态的sql
- PreparedStatement可以使用sql缓存区,效率比Statment高
- PreparedStatement可以有效防止sql注入,而Statment不能防止sql注入
- PreparedStatement支持操作’?'号位置参数,相对于Statement更加灵活
- PreparedStatement定义的时候放置sql语句,Statement先定义再放sql语句
三、JDBC操作事务
- JDBC中如何进行事务处理
- 事务是业务的最小单位,一个事务是由一条或多条对数据库操作的SQL语句所组成的一个不可分割的工作单元,只有当事务中的所有操作都正常执行了,整个事务才会被提交给数据库。在JDBC中,一般是通过commit()方法或rollback()方法来结束事务的操作。其中commit()方法表示完成对事务的提交,rollback()方法表示完成事务回滚,多用于在处理事务的过程中出现了异常的情况,这两种方法都位于java.sql.Connection类中。一般而言,事务默认操作是自动提交,即操作成功后,系统将自动调用commit()方法,否则将调用rollback()方法
- 当然,在JDBC中,也可以通过调用setAutoCommit(false)方法来禁止自动提交,然后就可以把多个数据库操作的表达式作为一个事务,在操作完成后调用commit()方法实现整体提交,如果其中一个表达式操作失败,就会抛出异常而不会调用commit()方法。在这种情况下,就可以在异常捕获的代码块中调用rollback()进行事务回滚。通过此种方法可以保持对数据库的多次操作后,数据仍然保持一致性
public class JDBCTransaction {
public static void main(String[] args) throws Exception {
new JDBCTransaction().trans_dept();
}
public void trans_dept() throws Exception {
PreparedStatement stmt=null ;
Connection conn =null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
conn.setAutoCommit(false);//开启事务
stmt =conn.prepareStatement("insert into dept values(?,?)");
//操作1
stmt.setInt(1,100);
stmt.setString(2,"test_1");
int m=stmt.executeUpdate();
int i = 1/0;//错误代码
//操作2
stmt.setInt(1,200);
stmt.setString(2,"test_2");
m=stmt.executeUpdate();
conn.commit();//事务提交
} catch (ClassNotFoundException e) {
e.printStackTrace();
try {
conn.rollback();//事务回滚
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally {
try {
if(stmt!=null) {
stmt.close();
}
if(conn!=null) {
conn.close();
conn.setAutoCommit(true);//关闭事务
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
四、Dao模式
- 确定数据库表
CREATE TABLE course(
cid INT primary key,
cname VARCHAR(28)
);
- 创建实体类
public class Course implements Serializable {
private int cid;
private String cname;
public Course(int cid, String cname) {
super();
this.cid = cid;
this.cname = cname;
}
public Course() {
super();
}
public int getCid() {
return cid;
}
public void setCid(int cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
@Override
public String toString() {
return "Course [cid=" + cid + ", cname=" + cname + "]";
}
}
- 数据库级别操作
public class DaoSupport {
protected Connection conn;
protected PreparedStatement pstmt;
protected Statement stmt;
protected ResultSet rs;
protected void connMysql() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
}
protected void closeMysql() {
try {
if(rs != null) {
rs.close();
}
if(stmt != null) {
stmt.close();
}
if(pstmt != null) {
pstmt.close();
}
if(conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
- 数据库表级操作
public interface CouDao {
//DML
public int update(String sql,Object...params);
//DQL
public List<Course> query(String sql,Object...params);//Object...params为可变
}
public class CouDaoImpl extends DaoSupport implements CouDao {
@Override
public int update(String sql, Object... params) {
int ret = 0;
try {
connMysql();
pstmt=conn.prepareStatement(sql);
int i=1;
for(Object param:params) {
pstmt.setObject(i, param);
i++;
}
ret = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
closeMysql();
}
return ret;
}
@Override
public List<Course> query(String sql, Object... params) {
List<Course> ret = new ArrayList<>();
try {
connMysql();
pstmt=conn.prepareStatement(sql);
int i=1;
for(Object param:params) {
pstmt.setObject(i, param);
i++;
}
rs=pstmt.executeQuery();
while(rs.next()) {
Course cou = new Course();
cou.setCid(rs.getInt("cid"));
cou.setCname(rs.getString("cname"));
ret.add(cou);
}
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}finally {
closeMysql();
}
return ret;
}
}
- 测试
public class CouTest {
public void testInsert() {
CouDaoImpl cdao = new CouDaoImpl();
String sql="insert into course(cid,cname) values(?,?)";
cdao.update(sql, 1,"gao1");
}
public void testUpdate() {
CouDaoImpl cdao = new CouDaoImpl();
String sql="update course set cid=?,cname=? where cid = ?";
cdao.update(sql, 2,"gao2",1);
}
public void testDelete() {
CouDaoImpl cdao = new CouDaoImpl();
String sql="delete from course where cid = ?";
cdao.update(sql, 1);
}
public void testSelect() {
CouDaoImpl cdao = new CouDaoImpl();
String sql="select * from course where cid = ?";
List<Course> list = cdao.query(sql, 1);
System.out.println(list);
}
public static void main(String[] args) {
new CouTest().testSelect();
}
}
五、JDBC的元数据
- 描述数据库或其组成部分的数据
- 分类:数据库元数据,SQL语句参数元数据,结果集元数据
- 数据库元数据
- 通过Connection对象获取有关数据库管理系统的各种信息,获取信息的方法在DatabaseMetaData类中
- DatabaseMetaData dbmd = connection.getMetaData();//通过Connection对象获取数据库元数据
- dbmd.getURL()//返回数据库URL
- dbmd.getUserName()//返回连接当前数据库管理里系统的用户名
- dbmd.isReadOnly()//指示数据库是否只允许读操作
- dbmd.getDatabaseProductName()//返回数据库名称
- dbmd.getDatabaseProductVersion()//返回数据库版本
- dbmd.getDriverName()//返回驱动名称
- dbmd.getDriverVersion()//返回驱动版本
- dbmd.getCatalogs()//返回数据库列表
- SQL语句参数元数据
- ParameterMetaData pmmd = prepareStatement.getParameterMetaData();//通过PreparedStatement对象获取参数元数据
- pmmd…getParameterCount()//返回参数个数
- 结果集元数据
- ResultSetMetaData rsmd = resultSet.getMetaData();//通过ResultSet对象获得结果集元数据
- rsmd.getColumnCount()//返回查询出来的总列数
- rsmd.getColumnName(索引)//返回指定列的列名,索引从1开始
- rsmd.getColumnLabel(索引)//返回指定列的别名
- rsmd.getColumnType(索引)//返回指定列的类型常量
- rsmd.getColumnTypeName(索引)//返回指定列的类型字符串
- rsmd.getGeneratedKeys()//返回包含新生成主键(GENERATED_KEY)的ResultSet对象
六、JDBC处理Blob
- MySQL中BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据,分为四种分别为TinyBlob(最大255)、Blob(最大65k)、MediumBlob(最大16m)、LongBlob(最大4g)
- 插入BLOB类型必须使用PreparedStatement
调用setBlob(int index,InputStream inputStream)输入//new FileInputStream(“图片输入地址”)
调用getBlob(int index).getBinaryStream()输出//new FileOutputStream(“图片输出地址”)
InputStream inputStream = new FileInputStream("图片输入地址");
pstmt.setBlob(i,inputStream);
InputStream in = rs.getBlob("图片字段").getBinaryStream();
OutputStream out = new FileOutputStream("图片输出地址");
byte[] buffer = new byte[1024];
int len = 0;
while((len = in.read(buffer)) != -1){
out.white(buffer,0,len);
}
七、批处理
- 当需要成批插入或更新采用批处理机制,包括两种方法:
addBatch(String)//添加需要批处理SQL语句或参数
executeBatch()//执行批处理语句 - 批处理SQL语句有两种
多条SQL语句的批量处理
...
Statement stmt = conn.createStatement();
stmt.addBatch(SQL1);
stmt.addBatch(SQL2);
...
stmt.addBatch(SQLn);
stmt.executeBatch();
...
一个SQL语句的批量传参
//原语句
...
int i=1;
for(Object param:params) {
pstmt.setObject(i, param);
i++;
}
rs=pstmt.executeQuery();
...
//批处理语句
...
int i=1;
for(Object param:params) {
pstmt.setObject(i, param);
i++;
pstmt.addBatch();
}
rs=pstmt.executeBatch();
八、连接池
- 传统模式问题
- 在主程序建立数据库连接,每次操作都要获取新的连接对象, 执行一次操作就把连接关闭, 而数据库创建连接通常需要消耗大量资源和时间, 连接资源没有重复利用,若多人同时在线,频繁进行数据库连接占有过多系统资源,可导致服务器崩溃
- 连接池原理
- 为数据库建立一个"缓冲池",创建一定数量的连接预先放入缓冲池,需建立连接时取出一个使用完后再放回
- 连接池初始化时创建一定数量的连接放到连接池中,无论这些连接是否被使用,连接池保证至少拥有最少数据库连接数的连接数量,最大数据库连接数设定了连接池的最大连接数,当超过时请求将被加入到等待队列中
- 连接池作用
- 实际开发中“获得连接”或“释放资源”是非常消耗系统资源的两个过程,为了解决此类性能问题,通常情况我们采用连接池技术,来共享连接Connection
- 用池来管理Connection,这样可以重复使用Connection。有了池,所以我们就不用自己来创建Connection,而是通过池来获取Connection对象。当使用完Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。池就可以再利用这个Connection对象了
- 优点
- 资源重用
- 更快的系统反映速度
- 新的资源分配手段
- 统一的连接管理,避免数据库连接泄露
- 常见的连接池有C3P0、DBCP
- C3P0开源免费的连接池,目前使用它的开源项目有:Spring、Hibernate等。使用第三方工具需要导入jar包,c3p0使用时还需要添加配置文件c3p0-config.xml
步骤:
1.创建C3P0数据源实例
ComboPooledDataSource ds = new ComboPooledDataSource();
2.为数据库指定必须的属性
ds.setJdbcUrl(“jdbc:mysql://localhost:3306/test”);
ds.setUser(“root”);
ds.setPassword(“123”);
ds.setDriverClass(“com.mysql.jdbc.Driver”);
ds.setAcquireIncrement(5);
ds.setInitialPoolSize(20);
ds.setMinPoolSize(2);
ds.setMaxPoolSize(50);
Connection con = ds.getConnection();
con.close();
//方式二
1.创建c3p0-config.xml文件(名字不能更改,且必须在src下)
2.创建C3P0数据源实例
DateSource ds = new ComboPooledDataSource(“配置文件中name属性名”);
3.从DateSource实例中获取数据库连接
Connection conn = ds.getConnection();//取代传统模式的获取连接的步骤
ComboPooledDataSource cd = (ComboPooledDataSource)ds; - DBCP开源的连接池,是Apache Common成员之一,在企业开发中也比较常见,tomcat内置的连接池,需要导入jar包
步骤:
1.创建DBCP数据源实例BasicDateSource ds = new BasicDateSource();
2.为数据库指定必须的属性
ds.setUsername(“值”);
ds.setPassword(“值”);
ds.setUrl(“值”);
ds.setDriverClassName(“值”);
ds.setInitialSize(数量);//设置连接池初始化连接数
ds.setMaxAction(数量);//指定最大连接数
ds.setMinIdle(数量);//指定最小连接数,在连接池中保存最少空闲连接数
ds.setMaxWait(数量);//等待连接池分配连接的最长时间,单位为毫秒
3.从数据源中获取数据库连接
Connection conn = ds.Connection();
//工厂设计模式
Properties p = new Properties();
InputStream in = 本类.class.getClassLoader().getResourceAsStream(“properties配置文件地址”);
p.load(in);
DataSourse dd = BasicDataSourceFactory.createDataSourse( p );
BasicDataSource ds = (BasicDataSource)dd;
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- 这是默认配置信息 -->
<default-config>
<!-- 连接四大参数配置 -->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbctest</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 池参数配置 -->
<property name="acquireIncrement">3</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">2</property>
<property name="maxPoolSize">10</property>
</default-config>
<!-- 专门为oracle提供的配置信息 -->
<named-config name="oracle-config">
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">123</property>
<property name="acquireIncrement">3</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">2</property>
<property name="maxPoolSize">10</property>
</named-config>
</c3p0-config>
九、DBUtils简介
- 概述:是Apache组织提供的JDBC工具类库,是对JDBC的简单封装
- 步骤:
- 创建QueryRunner实现类//QueryRunner qr = new QueryRunner();
编写SQL语句String sql = “dml语句”;
获取Connection conn = ds.Connection();
使用update方法更改//qr.update(conn,sql,位置参数1,位置参数2,…);
使用update方法查询//qr.query(conn,sql,ResultSetHandler);
关闭资源
- org.apache.commons.dbutils.QueryRunner类
- 该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量
- public Object query(Connection conn, String sql, Object[] params, ResultSetHandler rsh) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数,该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭
- public Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException:几乎与第一种方法一样,唯一的不同在于它不将数据库连接提供给方法,且它是从提供给构造方法的数据源(DataSource) 或使用的setDataSource 方法中重新获得Connection
- public Object query(Connection conn, String sql, ResultSetHandler rsh) throws SQLException:执行一个不需要置换参数的查询操作
- public int update(Connection conn, String sql, Object[] params) throws SQLException:用来执行一个更新(插入、更新或删除)操作
- public int update(Connection conn, String sql) throws SQLException:用来执行一个不需要置换参数的更新操作
- ResultSetHandler接口
- 该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式,ResultSetHandler接口提供了一个单独的方法:Object handle (java.sql.ResultSet.rs)
- 实现类
- BeanHandler(实体类对象):把结果集的第一条记录转换为创建BeanHandler对象时传入的class参数对应的对象(将结果集中的第一行数据封装到一个对应的JavaBean实例中)
- ArrayHandler:把结果集中的第一行数据转成对象数组
- ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里
- ColumnListHandler:将结果集中某一列的数据存放到List中
- KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
- DbUtils类
- 提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的
- public static void close(…) throws java.sql.SQLException:DbUtils类提供了三个重载的关闭方法,这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet
- public static void commitAndCloseQuietly(Connection conn):用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常
- public static boolean loadDriver(java.lang.String driverClassName):这一方装载并注册JDBC驱动程序,如果成功就返回true,使用该方法,你不需要捕捉这个异常ClassNotFoundException
十、JDBC调用存储过程&函数
- 步骤
- 1.通过Connection对象的prepareCall()方法创建一个CallableStatement对象实例
注:在使用Connection对象的prepareCall()方法时需要传入一个String类型的字符串,该字符串用于指明如何调用存储过程 - 2.通过CallableStatement对象的registerOutParameter() 方法注册Out参数
- 3.通过CallableStatement对象的setXxx()方法设定IN或In out参数,若想将参数设为null,可以使用setNUll()
- 4.通过CallableStatement对象的execute()方法执行存储过程
- 5.如果所调用的是带返回参数的存储过程没还需要通过CallableStatement对象的getXxx()函数进行获取
- 调用函数和存储过程的sql语句的区别
- 调用存储函数 1.{?= call [(,, …)]}
- 调用存储过程 2.{call [(,, …)]}
@Test
public void testCallableStatement() {
Connection connection = null;
String sql = "{?= call 过程名[(参数1,参数2, ...)]}";
CallableStatement callableStatement = null;
try {
connection = DBCTools.getConnection();
callableStatement = connection.prepareCall(sql);
callableStatement.registerOutParameter(1, Types.NUMERIC);
callableStatement.registerOutParameter(3, Types.NUMERIC);
callableStatement.setInt(2, 80);
double sumSalary = callableStatement.getDouble(1);
long empCount = callableStatement.getLong(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, callableStatement, connection);
}
}