一、概述

java连接数据库技术,是一项由sun公司发布的用于java连接数据库的通用标准,这套标准由一系列的接口(Connection,Statement,ResultSet等)构成,旨在屏蔽不同数据库之间的差异,为多种关系型数据库提供统一访问,使得开发者无需关注不同数据库系统之间的差异,简化java连接数据库的步骤,使用前需导入不同数据库的驱动包

二、JDBC操作数据库步骤

  1. 加载驱动程序
  2. 获取数据库连接对象
  3. 创建一个执行列表(载体)
  4. 执行SQL语句(DML,DQL)
  5. 处理结果集
  6. 关闭资源
//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模式

  1. 确定数据库表
CREATE TABLE course(
	cid INT primary key,
	cname VARCHAR(28)
);
  1. 创建实体类
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 + "]";
	}
}
  1. 数据库级别操作
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();
		}
	}
}
  1. 数据库表级操作
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;
	}

}
  1. 测试
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的元数据

  1. 描述数据库或其组成部分的数据
  2. 分类:数据库元数据,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);
    }
}