JDBC
1.JDBC概述
JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成,是Java访问数据库的标准规范。
JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。
JDBC需要连接驱动,驱动是两个设备要进行通信,满足一定通信数据格式,数据格式由设备提供商规定,设备提供商为设备提供驱动软件,通过软件可以与该设备进行通信。
今天我们使用的是mysql的驱动mysql-connector-java-5.1.39-bin.jar
2.JDBC原理
Java提供访问数据库规范称为JDBC,而生产厂商提供规范的实现类称为驱动。
JDBC是接口,驱动是接口的实现,没有驱动将无法完成数据库连接,从而不能操作数据库。每个数据库厂商都需要提供自己的驱动,用来连接自己公司的数据库,也就是说驱动一般都由数据库生产厂商提供。
3.JDBC开发步骤
3.1导入驱动jar包
创建lib目录,用于存放当前项目需要的所有jar包。
选择jar包,右键执行build path / Add to Build Path。
3.2开发步骤
-
注册驱动
告知JVM使用的是哪一个数据库的驱动。
-
Class.forName("com.mysql.jdbc.Driver")
利用反射技术,将驱动类加入到内存中,通过全限定类名(包名+类名)获得对象,
Class clazz=Class.forName("全限定类名");
-
DriverManager.registerDriver(new com.mysql.jdbc.Driver())
不推荐使用这种方式,存在两方面不足:硬编码,后期不易于程序扩展和维护;驱动被注册两次(new Driver执行了源代码中的静态方法,源码中的静态方法也执行了一次注册程序)。
-
-
获得连接
获取连接需要方法 DriverManager.getConnection(url,username,password)
url:需要连接数据库的位置(网址);username:用户名;password:密码。
Connection con= DriverManager.getConnection("jdbc:mysql://localhost:3306/rev1_jdbc","root", "root123.");
jdbc:mysql://localhost:3306/rev1_jdbc
JDBC规定url的格式由三部分组成,每个部分中间使用冒号分隔。
-
第一部分是jdbc,这是固定的;
-
第二部分是数据库名称,那么连接mysql数据库,第二部分当然是mysql了;
-
第三部分是由数据库厂商规定的,由数据库服务器的IP地址(localhost)、端口号(3306),以及DATABASE名称(rev1_jdbc)组成。
-
-
获得语句执行平台
Statement st=con.createStatement();
-
执行sql语句
String sql="select * from sort"; ResultSet rs=st.executeQuery(sql);//查询
常用方法:
-
int executeUpdate(String sql); 执行insert update delete语句,返回值int表示影响的行数
-
ResultSet executeQuery(String sql); 执行select语句
-
boolean execute(String sql); 执行select返回true,执行其它语句返回false
-
-
处理结果集
执行insert,update,delete无需处理。
while(rs.next()) { System.out.println(rs.getInt("sid")+" "+rs.getString("sname")); }
ResultSet实际上就是一张二维的表格,调用其boolean next()方法指向某行记录,当第一次调用next()方法时,便指向第一行记录的位置。
常用方法:
-
Object getObject(int index) / Object getObject(String name) :获得任意对象
-
String getString(int index) / Object getObject(String name) :获得字符串
-
int getInt(int index) / Object getObject(String name): 获得整型
-
double getDouble(int index) / Object getObject(String name) :获得双精度浮点型
-
-
释放资源
与IO流一样,使用后的东西都需要关闭,关闭顺序是先得到的后关闭,后得到的先关闭。
rs.close();//关闭结果集 st.close();//关闭sql语句执行平台 con.close();//关闭连接
4.SQL注入问题
假设有登录案例SQL语句如下:
SELECT * FROM 用户表 WHERE NAME = 用户输入的用户名 AND PASSWORD = 用户输的密码;
当用户输入正确的账号与密码后,查询到了信息则让用户登录。但是当用户输入的账号为XXX,密码为:XXX’ OR ‘a’=’a时,则真正执行的代码变为:
SELECT * FROM 用户表 WHERE NAME = ‘XXX’ AND PASSWORD =’ XXX’ OR ’a’=’a’;
此时,上述查询语句时永远可以查询出结果的。那么用户就直接登录成功了,显然我们不希望看到这样的结果,这便是SQL注入问题。
为此,我们使用PreparedStatement预处理对象来解决对应的问题。
4.1预处理对象PreparedStatement
相比较以前的statement, 预先处理给定的sql语句,对其执行语法检查。 在sql语句里面使用 ? 占位符来替代后续要传递进来的变量。 后面进来的变量值,将会被看成是字符串,不会产生任何的关键字。
String sql="select * from user where uname=? and upassword=?";
PreparedStatement ps=con.prepareStatement(sql);
/*
* 可以直接调用ps对象的setObject方法(比较省事),void setObject(int parameterIndex, Object x), 使用给定对象设置指定参数的值 ,第一个参数代表第几个?,第二个代表?中应填入的值。从左到右数过来,第一个?为1
*/
ps.setObject(1, "tom");//或ps.setString(1,"tom")
ps.setObject(2, p);
4.2预处理对象executeUpdate方法
- 开发步骤
- 注册驱动
- 获取连接
- 获取预处理对象
- sql语句占位符设置实际参数
- 执行sql语句
- 释放资源
4.2.1插入记录insert
//向分类表中插入新的分类
public class Test2 {
public static void main(String[] args) throws Exception {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/rev1_jdbc", "root", "root123.");
//3.获取预处理对象
String sql="insert into sort values(null,?)";
PreparedStatement ps=con.prepareStatement(sql);
//4.设置sql语句参数
ps.setString(1, "方便面");
//5.执行sql语句
int i=ps.executeUpdate();
System.out.println(i);
//6.关闭资源
ps.close();
con.close();
}
}
4.2.2更新记录upate
//更新分类表中指定分类ID所对应记录的分类名称
public class Test3 {
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/rev1_jdbc", "root", "root123.");
String sql="update sort set sname=? where sid=?";
PreparedStatement ps=con.prepareStatement(sql);
ps.setString(1, "面包");
ps.setInt(2, 8);
int i=ps.executeUpdate();
System.out.println(i);
ps.close();
con.close();
}
}
4.2.3删除记录delete
//删除分类表中指定分类ID的记录
public class Test4 {
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/rev1_jdbc", "root", "root123.");
String sql="delete from sort where sid=?";
PreparedStatement ps=con.prepareStatement(sql);
ps.setInt(1, 8);
int i=ps.executeUpdate();
System.out.println(i);
ps.close();
con.close();
}
}
4.3预处理对象executeQuery方法
通过预处理对象的executeQuery方法,完成记录的select语句的执行。
- 开发步骤
- 注册驱动
- 获取连接
- 获取预处理对象
- sql语句占位符设置实际参数
- 执行sql语句
- 处理结果集
- 释放资源
4.3.1查询记录select
- 查询分类表所有记录
//查询分类表所有记录
public class Test5 {
public static void main(String[] args) throws Exception {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/rev1_jdbc", "root", "root123.");
//3.获取预处理对象
String sql="select * from sort";
PreparedStatement ps=con.prepareStatement(sql);
//4.设置sql语句参数
//5.执行sql语句
ResultSet rs=ps.executeQuery();
//6.处理结果集
while(rs.next()) {
System.out.println(rs.getInt("sid")+" "+rs.getString("sname"));
}
//7.关闭资源
rs.close();
ps.close();
con.close();
}
}
- 查询分类表中指定分类名称的记录
public class Test6 {
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/rev1_jdbc", "root", "root123.");
String sql="select * from sort where sname=?";
PreparedStatement ps=con.prepareStatement(sql);
ps.setString(1, "酒水");
ResultSet rs=ps.executeQuery();
while(rs.next()) {
System.out.println(rs.getInt("sid")+" "+rs.getString("sname"));
}
rs.close();
ps.close();
con.close();
}
}
5.JDBC工具类
获得数据库连接操作,在以后的增删改查所有功能中都存在,可以封装工具类JDBCUtils,提供获取连接对象的方法,从而达到代码的复用。
public class JDBCUtils {
private static final String DRIVERNAME="com.mysql.jdbc.Driver";
private static final String URL="jdbc:mysql://localhost:3306/rev1_jdbc";
private static final String USERNAME="root";
private static final String PASSWORD="root123.";
static{
try {
Class.forName("DRIVERNAME");
} catch (ClassNotFoundException e) {
System.out.println("数据库驱动注册失败");
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USERNAME, PASSWORD);
}
}
6.properties配置文件
6.1使用properties配置文件
开发中获得连接的4个参数(驱动、URL、用户名、密码)通常都存在配置文件中,方便后期维护,程序如果需要更换数据库,只需要修改配置文件即可。
properties文件要求:
-
文件位置:任意,建议src下
-
文件名称:任意,扩展名为properties
-
文件内容:一行一组数据,格式是key=value.
-
key命名自定义,如果是多个单词,习惯使用点分隔,如:jdbc.driver
-
value值不支持中文,如果需要使用非英文字符,将进行unicode转换
-
6.2创建配置文件
在项目src目录下,创建db.properties文件。
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/rev1_jdbc
user=root
password=root123.
6.3加载配置文件:properties对象
加载properties文件获得流,然后使用Properties对象进行处理。
public class JDBCUtils {
private static String driver;
private static String url;
private static String user;
private static String password;
static {
try {
//1.使用Properties处理流
Properties pro=new Properties();
//使用类加载器(在src目录下找到这个文件并加载进去)
/*
* 要拿到类加载器,必须先拿到这个类的class对象
* ClassLoader getClassLoader() 返回该类的类加载器
*InputStream getResourceAsStream(String name)返回读取指定资源的输入流
*/
InputStream is=
JDBCUtils.class.getClassLoader().getResourceAsStream("db.properties");
pro.load(is);
/*
或者:
Reader reader=new FileReader("db.properties");
pro.load(reader);
*/
//2.使用getProperty(key)获取值
driver=pro.getProperty("driver");
url=pro.getProperty("url");
user=pro.getProperty("url");
password=pro.getProperty("password");
} catch (IOException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws Exception {
Class.forName(driver);
return DriverManager.getConnection(url, user, password);
}
}
7.DBUtils
如果只使用JDBC进行开发,我们会发现冗余代码过多,为了简化JDBC开发,采用apache commons组件一个成员:DBUtils。DBUtils就是JDBC的简化开发工具包。需要项目导入commons-dbutils-1.6.jar才能够正常使用DBUtils工具。
7.1概述
DBUtils是java编程中的数据库操作实用工具,小巧简单实用。
DBUtils封装了对JDBC的操作,简化了JDBC操作,可以少写代码。
Dbutils三个核心功能介绍:
-
QueryRunner中提供对sql语句操作的API
-
ResultSetHandler接口,用于定义select操作后,怎样封装结果集
-
定义了关闭资源与事务处理的方法
7.2QueryRunner核心类
-
update(Connection conn, String sql, Object... params) ,用来完成表数据的增加、删除、更新操作
-
query(Connection conn, String sql, ResultSetHandler
rsh, Object... params) ,用来完成表数据的查询操作
7.3QueryRunner实现增加,删除,修改操作
- 增加
@Test
public void insert() throws SQLException {
QueryRunner qr=new QueryRunner();
String sql="insert into sort values(null,?)";
String param="办公用品";
Connection con=JDBCUtils.getConnection();
int line=qr.update(con,sql, param);
System.out.println(line);
}
- 删除
@Test
public void delete() throws SQLException {
QueryRunner qr=new QueryRunner();
String sql="delete from sort where sid=?";
int param=11;
Connection con=JDBCUtils.getConnection();
int line=qr.update(con,sql, param);
System.out.println(line);
}
- 修改
@Test
public void update() throws SQLException {
QueryRunner qr=new QueryRunner();
String sql="update sort set sname=? where sid=?";
Object[] params= {"面包",9};
Connection con=JDBCUtils.getConnection();
int line=qr.update(con,sql, params);
System.out.println(line);
}
7.4QueryRunner实现查询操作
query(Connection conn, String sql, ResultSetHandler
7.4.1ResultSetHandler结果集处理类
- ArrayHandler
将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是第一条记录的每一列字段的值。
@Test
public void testArrayHandler() throws Exception {
QueryRunner qr=new QueryRunner();
String sql="select * from sort";
Connection con=JDBCUtils.getConnection();
Object[] result=qr.query(con, sql, new ArrayHandler());
for(Object o:result) {
System.out.println(o);
}
}
- ArrayListHandler
将结果集中的每一条记录都封装到一个Object[]数组中,再将这些数组封装到List集合中。
@Test
public void testArrayListHandler() throws Exception {
QueryRunner qr=new QueryRunner();
String sql="select * from sort";
Connection con=JDBCUtils.getConnection();
List<Object[]> result=qr.query(con, sql, new ArrayListHandler());
for(Object[] obj:result) {
for(Object o:obj) {
System.out.print(o+" ");
}
System.out.println();
}
}
- BeanHandler
将结果集中第一条记录封装到一个指定的JavaBean中,注意此JavaBean中必须有空参构造。
JavaBean就是一个类,在开发中常用封装数据。具有如下特性:
需要实现接口:java.io.Serializable ,通常实现接口这步骤省略了,不会影响程序;
提供私有字段:private 类型 字段名;
提供getter/setter方法;
提供无参构造。
@Test
public void testBeanHandler() throws Exception {
QueryRunner qr=new QueryRunner();
String sql="select * from sort";
Connection con=JDBCUtils.getConnection();
Sort result=qr.query(con, sql, new BeanHandler<Sort>(Sort.class));
System.out.println(result.getSid()+" "+result.getSname());
}
public class Sort {
private int sid;
private String sname;
public Sort() {
super();
}
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
}
- BeanListHandler
将结果集中每一条记录封装到指定的JavaBean中,再将这些JavaBean封装到List集合中。
@Test
public void testBeanListHandler() throws Exception {
QueryRunner qr=new QueryRunner();
String sql="select * from sort";
Connection con=JDBCUtils.getConnection();
List<Sort> result=qr.query(con, sql, new BeanListHandler<Sort>(Sort.class));
for(Sort sort:result) {
System.out.println(sort.getSid()+" "+sort.getSname());
}
}
- ColumnListHandler
将结果集指定列的字段值封装到一个List集合中。
@Test
public void testColumnListHandler() throws Exception {
QueryRunner qr=new QueryRunner();
String sql="select * from sort";
Connection con=JDBCUtils.getConnection();
List<String> result=qr.query(con, sql, new ColumnListHandler<String>("sname"));
//sname类型为String,所以填String。如果不确定具体类型,可以写Object
for(String s:result) {
System.out.println(s);
}
}
- ScalarHandler
只适用于查询后只有单条数据,例如 select count(*) from 表操作。
@Test
public void testScalarHandler() throws Exception {
QueryRunner qr=new QueryRunner();
String sql="select count(*) from sort";
Connection con=JDBCUtils.getConnection();
Object o=qr.query(con, sql, new ScalarHandler<Object>());
System.out.println(o);
}
- MapHandler
将结果集第一行数据封装到Map双列集合中:Map<键,值> 键:列名,值:这列的数据。
@Test
public void testMapHandler() throws Exception {
QueryRunner qr=new QueryRunner();
String sql="select * from sort";
Connection con=JDBCUtils.getConnection();
Map<String,Object> map=qr.query(con, sql, new MapHandler());
for(String s:map.keySet()) {
System.out.print(map.get(s)+" ");
}
}
- MapListHandler
将结果集每一行数据封装到一个Map双列集合中,键:列名,值:数据,再将多个Map集合存储到List集合中。
@Test
public void testMapListHandler() throws Exception {
QueryRunner qr=new QueryRunner();
String sql="select * from sort";
Connection con=JDBCUtils.getConnection();
List<Map<String,Object>> result=qr.query(con, sql, new MapListHandler());
for(Map<String,Object> map:result) {
for(String s:map.keySet()) {
System.out.print(map.get(s)+" ");
}
System.out.println();
}
}
8.连接池
实际开发中“获得连接”和“释放资源”是非常消耗系统资源的两个过程,为了解决此类性能问题,通常采用连接池技术,来共享连接Connection。这样就不需要每次都创建连接、释放连接了,这些操作都交给了连接池。
8.1连接池概述
有了池,就不用自己来创建Connection,而是通过池来获取Connection对象。当使用Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection归还给池,池就可以再利用这个Connection对象了。
Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池。
常见的连接池:DBCP、C3P0。
8.2DBCP连接池
DBCP是一个开源的连接池,是Apache Common成员之一,在企业开发中也比较常见,tomcat内置的连接池。
8.2.1导入jar包
8.2.2编写工具类
DataSource :是java中提供的连接池,作为 DriverManager工具的替代项。在DBCP包中提供了DataSource接口的实现类BasicDataSource类。
public class JDBCUtils {
private static final String DRIVERNAME="com.mysql.jdbc.Driver";
private static final String URL="jdbc:mysql://localhost:3306/rev1_jdbc";
private static final String USERNAME="root";
private static final String PASSWORD="root123.";
//创建连接池
public static BasicDataSource dataSource=new BasicDataSource();
//静态代码块,对连接池对象进行基本的配置
static {
dataSource.setDriverClassName(DRIVERNAME);
dataSource.setUrl(URL);
dataSource.setUsername(USERNAME);
dataSource.setPassword(PASSWORD);
}
public static DataSource getConnection() throws SQLException {
return dataSource;
}
}
常见配置项:
8.2.3工具类的使用
- 增加操作
@Test
public void insert() throws SQLException {
//获取一个用来执行sql语句的QueryRunner对象
QueryRunner qr=new QueryRunner(JDBCUtils.getDataSource());
String sql="insert into sort values(null,?)";
String param="办公用品";
int line=qr.update(sql, param);
System.out.println(line);
}
- 删除操作
@Test
public void delete() throws SQLException {
QueryRunner qr=new QueryRunner(JDBCUtils.getDataSource());
String sql="delete from sort where sid=?";
int param=11;
int line=qr.update(sql, param);
System.out.println(line);
}
- 修改操作
@Test
public void update() throws SQLException {
QueryRunner qr=new QueryRunner(JDBCUtils.getDataSource());
String sql="update sort set sname=? where sid=?";
Object[] params= {"面包",9};
int line=qr.update(sql, params);
System.out.println(line);
}
- 查询操作
@Test
public void query() throws SQLException {
QueryRunner qr=new QueryRunner(JDBCUtils.getDataSource());
String sql="select * from sort where sid>?";
int param=5;
List<Sort> list=qr.query(sql, new BeanListHandler<Sort>(Sort.class), param);
for(Sort sort:list) {
System.out.println(sort.getSid()+" "+sort.getSname());
}
}
8.3C3P0连接池
8.3.1导入jar包
c3p0-0.9.1.2.jar
8.3.2不使用配置文件
public class Test8 {
private static ComboPooledDataSource dataSource=null;
static {
try {
//1.创建datasource
dataSource=new ComboPooledDataSource();
//2.设置连接的数据信息
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/rev1_jdbc");
dataSource.setUser("root");
dataSource.setPassword("root123.");
} catch (PropertyVetoException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws Exception {
return dataSource.getConnection();
}
}
8.3.3使用配置文件
c3p0的配置文件支持 properties , 也支持 xml 方式。 不过开发中,一般使用xml方式来配置。
- 使用db.properties
public class Test9 {
private static ComboPooledDataSource dataSource=null;
static {
try {
//1.创建datasource
dataSource=new ComboPooledDataSource();
//2.设置连接的数据信息
Properties ps=new Properties();
InputStream is=JDBCUtils.class.getClassLoader().getResourceAsStream("db.properties");
ps.load(is);
dataSource.setDriverClass(ps.getProperty("driver"));
dataSource.setJdbcUrl(ps.getProperty("url"));
dataSource.setUser(ps.getProperty("user"));
dataSource.setPassword(ps.getProperty("password"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws Exception {
return dataSource.getConnection();
}
}
- 使用xml
src下, 创建xml文件,名为:c3p0-config.xml,注意此处,名字需要固定了。
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost/user</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 可选配置 -->
<property name="initialPoolSize">5</property>
</default-config>
</c3p0-config>
public class Test10 {
private static ComboPooledDataSource dataSource=null;
static {
try {
dataSource=new ComboPooledDataSource();
Properties ps=new Properties();
Reader r=new FileReader("c3p0-config.xml");
ps.load(r);
dataSource.setDriverClass(ps.getProperty("driverClass"));
dataSource.setJdbcUrl(ps.getProperty("jdbcUrl"));
dataSource.setUser(ps.getProperty("user"));
dataSource.setPassword(ps.getProperty("password"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws Exception {
return dataSource.getConnection();
}
}