回顾总结21
JDBC
JDBC(java.sql和javax.sql)是为了访问不同的数据库提供了统一的接口(JDBC API),这些接口统一和规范了应用程序与数据库的连接,执行SQL语句,并得到返回结果等各类操作,为使用者屏蔽了细节问题。
Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。
Java可以不直接连接数据库,只提供一个接口,接口里面有一系列的方法让数据库层面去实现,从而获得实现该接口的类。而我们就可以使用这些类来操作相应的数据库
JDBC连接数据库
方式一:
//JDBC编写步骤
//1.注册驱动
Driver driver = new Driver();
//2.获取连接
String url = "jdbc:mysql://localhost:3306/db02";
//将 数据库的用户名和密码放入到Properties对象
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","1347");
//获取连接
Connection connect = driver.connect(url,properties);
//3.执行SQL
String sql = "insert into actor values(null,'刘德华','男','1970-11-11','788787')";
Statement statement = connect.createStatement();
//statement用于执行静态sql语句并返回生成结果的信息
int rows = statement.executeUpdate(sql);
System.out.println(rows > 0 ? "成功" : "失败" );
//4.关闭连接
statement.close();
connect.close();
方式二:
//通过反射加载Driver类,动态加载,更加的灵活,减少依赖性
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();
方式三:
//使用DriverManager来管理Driver,有更好的扩展性
//1.先获取一个Driver对象
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();
//2.注册Driver
DriverManager.registerDriver(driver);
//3.得到url和配置文件
String url = "jdbc:mysql://localhost:3306/db02";
//4.将 数据库的用户名和密码放入到Properties对象
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","1347");
//5.获取连接,使用DriverManager
Connection connection = DriverManager.getConnection(url,properties);
方式四:
最常用
//使用Class.forName会自动完成注册驱动
//底层进行了优化,会帮我们进行driver的注册
//在mysql驱动5.1.6以后,Class.forName也可省略。
//因为jdk1.5以后使用了jdbc4,不再需要显示调用class.forName,而是自动调用
//在jar包下的META-INF\services\java.sql.Driver文本中的类名去注册
//但是老师建议写上这句话,更加明确,可读性高。
Class.forName("com.mysql.jdbc.Driver");
//得到url和配置信息
String url = "jdbc:mysql://localhost:3306/db02";
//将 数据库的用户名和密码放入到Properties对象
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","1347");
//获取连接
Connection connection = DriverManager.getConnection(url,prop
方式五:
建议使用
//得到url和配置信息
//将 数据库的用户名和密码放入到Properties对象
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
//会自动完成注册驱动.建议写上
Class.forName(driver);
//获取连接
Connection connection = DriverManager.getConnection(url,user,password);
ResultSet结果集
- 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
- ResultSet对象保持一个光标指向其当前的数据行,最初,光标位于表头
- next方法将光标移动到下一行,并且由于ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集
获取查询表的数据,通过exectuQuery返回一个ResultSet集合,集合中存储了查询表的数据,我们可以通过方法来进行读取
每一个ResultSet底层是ArrayList,ArrayList每一行的数据是使用ByteArrayRow存储的
//获得连接数据库的参数,账号、密码、数据库、驱动
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
//1.注册驱动,通过反射方式
Class.forName(driver);
//2.获取数据库连接
Connection connection = DriverManager.getConnection(url,user,password);
//3.写sql语句,获得操作sql语句的statement
String sql = "select name,pwd from admin";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//获取结果集,并打印输出
ResultSet resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
String name = resultSet.getString(1);
String pwd = resultSet.getString(2);
System.out.println(name + "\t" + pwd);
}
//关流
resultSet.close();
preparedStatement.close();
connection.close();
Statement
- Statement对象,用于执行SQL语句并返回其生成结果
- 连接建立后,需要对数据库进行访问,执行命令或是SQL语句,可以通过
- Statement【存在SQL注入】
- PreparedStatement【预处理】
- CallableStatement【存储过程】
- Statement对象执行SQL语句,存在SQL注入风险
- SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据注入非法的SQL语句段或命令,恶意攻击数据库
- 防范SQL注入,只要用PreparedStatement取代Statement就可以了
SQL注入
通过传入的用户名和密码,改动查询条件,达到绕过验证的操作
比如一条查询语句是 SELECT * FROM WHERE id = '账号' AND pwd = '密码'
只有当账号密码和数据库中的数据对应上,才能登录进去
现在我们尝试SQL注入,改变查询条件,输入id = '(1'or)' AND pwd = '(or'1' = '1)'
将查询条件改为了 SELECT * FROM WHERE (id = '1') OR ('AND pwd =') OR ('1' = '1')
preparedStatement优点
- 不再使用 + 拼接sql语句,减少语法错误
- 有效解决了sql注入问题
- 大大减少了编译次数,效率较高
//获得连接数据库的参数,账号、密码、数据库、驱动
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
//1.注册驱动,通过反射方式
Class.forName(driver);
//2.获取数据库连接
Connection connection = DriverManager.getConnection(url,user,password);
//增
String sql = "insert into admin values (?,?)";
//改
//String sql = "update admin set name = ? where name = ?";
//删
//String sql = "delete from admin where name = ?";
//获得操作sql语句的statement
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"king");
preparedStatement.setString(2,"tom");
//返回语句执行结果
int i = preparedStatement.executeUpdate();
System.out.println(i > 0 ? "成功" : "失败" );
//关流
preparedStatement.close();
connection.close();
JDBC API小结
DriverManager驱动管理类
getConnection(url,user,pwd) :获取到连接
Connection接口
createStatement:创建Statement对象,有sql注入风险
prepareStatement(sql):生成preparedStatement预处理对象,解决了sql注入
Statement接口
executeUpdate(sql):执行dml语句,返回被影响的行数
executeQuery(sql):执行查询,返回ResultSet对象
execute(sql):执行任意的sql,返回布尔值
PreparedStatement接口
executeUpdate(sql):执行dml语句,返回被影响的行数
executeQuery(sql):执行查询,返回ResultSet对象
execute(sql):执行任意的sql,返回布尔值
setXxx(占位符索引,占位符值):用来替换前面的占位符,接收类型为Xxx
setObject(占位符索引,占位符的值):当做对象处理,接收类型为Object
ResultSet结果集
next:向下移动一行,如果没有下一行,就返回false
previous:向上移动一行,如果没有上一行,返回false
getXxx(列的索引名)或者getXxx(列名):获取对应列的值,接收类型为Xxx
getObject(列的索引名)或者getXxx(列名):同上,接收类型为Object
事务
JDBC开启事务,不使用start transaction,使用setautocommit为false,停止事务的自动提交相当于开启你自己的事务。
批处理
-
当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理,通常情况下比单独提交处理更有效率
-
JDBC的批处理语句包括下面方法
addBatch:添加需要批量处理的SQL语句或参数
executeBatch:执行批量处理语句
clearBatch:清空批处理包的语句
-
批处理底层是ArrayList,通过将sql语句放入arrayList,满足条件后,将arrayList里的sql语句一次性传输给mysql执行。
details
-
如果要使用批处理,需要在配置文件的url添加?rewriteBatchedStatements=true
-
sql语句要写的规范。
例如insert into admin values(null,?,?),批处理不生效。
在values和插入数据间加个空格就好了。
insert into admin values (null,?,?)
数据库连接池
传统连接Connection问题分析
- 传统的JDBC数据库连接使用DriverManager获取,每次向数据库建立连接的时候都要将Connection加载到内存中,再验证IP地址,用户名和密码(0.05s~1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃
- 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄露,最终将导致重启数据库
- 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄露,Mysql崩溃
- 解决传统开发中的数据库连接问题,可以采用数据库连接池技术。(connection pool)
数据库连接池
- 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从"缓冲池"中取出一个,使用完毕之后再放回去
- 数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中
数据库连接池种类
JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供实现
- C3P0数据库连接池:速度相对较慢,稳定性不错(应用在hibernate,spring)
- DBCP数据库连接池:速度相对C3P0较快,但是不稳定
- Proxool数据库连接池:有监控连接池状态的功能,稳定性较C3P0差一点
- BoneCP数据库连接池:速度快
- Druid(德鲁伊)是阿里提供的数据库连接池,集DBCP、C3P0、Proxool优点于一身的数据库连接池
传统方式连接数据库
#mysql.properties配置文件
url=jdbc:mysql://localhost:3306/db02?rewriteBatchedStatements=true
user=root
password=1347
driver=com.mysql.jdbc.Driver
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
Class.forName(driver);
Connection connection = DriverManager.getConnection(url,user,password);
C3P0连接池
首先要引用对应jar包
方式一
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
//创建数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
//给数据源设置相关的参数
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
//设置初始化连接数和最大连接数
comboPooledDataSource.setInitialPoolSize(10);
comboPooledDataSource.setMaxConnectionAge(50);
//获取连接,从连接池中拿取连接
Connection connection = comboPooledDataSource.getConnection();
方式二
//使用c3p0的xml配置文件,将所有信息写入xml文件,获取连接池的时候直接读取//xml文件内容如下,文件名为c3p0-config.xml<c3p0-config> <!-- 连接池的名称 --> <named-config name="comboPool_name"> <!-- 驱动类 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <!-- url --> <property name="jdbcUrl">jdbc:mysql://localhost/db02</property> <!-- 数据库用户名 --> <property name="user">root</property> <!-- 密码 --> <property name="password">1347</property> <!-- 每次增长的连接数 --> <property name="acquireIncrement">5</property> <!-- 初始的连接数 --> <property name="initialPoolSize">10</property> <!-- 最小连接数 --> <property name="minPoolSize">5</property> <!-- 最大连接数 --> <property name="maxPoolSize">10</property> <!-- 可连接的最多的命令对象数 --> <property name="maxStatements">5</property> <!-- 每个连接对象可连接的最多的命令对象数 --> <property name="maxStatementsPerConnection">2</property> </name-config></c3p0-config>
//有了xml文件之后使用c3p0连接池//创建数据源对象(连接池)ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("comboPool_name");//获取连接,从连接池中拿取连接Connection connection = comboPooledDataSource.getConnection();
Druid连接池
引入druid的jar包
#druid.properties配置文件#key=valuedriverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/db02?rewriteBatchedStatements=true#url=jdbc:mysql://localhost:3306/db02username=rootpassword=1347#initial connection SizeinitialSize=10#min idle connection sizeminIdle=5#max active connection sizemaxActive=20#max wait time (5000 mil seconds)maxWait=5000
//引入配置文件数据Properties properties = new Properties();properties.load(new FileInputStream("src\\druid.properties"));//使用Druid创建数据源(连接池)传入配置文件参数DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);Connection connection = dataSource.getConnection();connection.close();
Apache--DBUtils
原始方法完成数据转换
连接的事情告一段落之后,我们开始对性能和便利方面进行学习
对于resultSet的许多问题
- 结果集合connection是关联的,即如果关闭连接,就不能使用结果集
- 结果集不利于数据管理,只能使用一次
- 使用返回信息也不方便
之前我们是连接后通过JDBC把java语句转化成sql语句,去数据库中尽心查找,然后返回数据库查找的内容。然后关闭连接。
这会导致很多不方便的操作。我们现在需要的是在java层面,获取数据,并且进行操作。在数据库的连接关闭之后,我们也能留住数据。
因此我们可以根据数据库中的表,去设计对应的一个类。表对应类,字段对应字段,获取数据对应方法。一个实例对象对应一条数据库记录,然后存放到ArrayList集合中
//Actor表对应的类public class Actor { private Integer id; private String name; private String sex; private Date bornDate; private String phone; //还有无参构造、有参构造、getset、toString}
//
Connection connection = null;
String sql = "select * from actor where id >= ?";
PreparedStatement preparedStatement = null;
ResultSet set = null;
ArrayList<Actor> list = new ArrayList<>();
try {
connection = JDBCUtilsByDruid.getConnection();
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,1);
set = preparedStatement.executeQuery();
while (set.next()){
int id = set.getInt("id");
String name = set.getString("name");
String sex = set.getString("sex");
Date bornDate = set.getDate("bornDate");
String phone = set.getString("phone");
list.add(new Actor(id,name,sex,bornDate,phone));
}
System.out.println(list);
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtilsByDruid.close(set,preparedStatement,connection);
}
Apache介绍
- commons-dbutils是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量
DbUtils类
- QueryRunner类:该类封装了SQL的执行,是线程安全的,开源实现增、删、改、查、批处理
- 使用QueryRunner类实现查询
- ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式
常用方法
- ArrayHandler:把结果集中的第一行数据转成对象数组
- ArrayListHandler:每一行都转成一个数组,再存放到List中
- BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中
- BeanListHandler:同上
- ColumnListHandler:将结果集中某一列的数据存放到List中
- KeyedHandler(name):将结果集中每行数据都封装到Map里,再把这些Mao存到一个Map里,其key为指定的key
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value是对应的值
- MapListHandler:同上
总结
以上相当于完成了在Java程序里对数据库进行操作,但是要操作所有的表,增删改查,没有区分,sql语句是写死的,每操作一次都需要一个新的sql语句。极不方便。
通过下面的方法,我们归类sql语句,将每个表的sql语句单独区分出来,划到各个表的DAO里进行操作增删改查。
DAO(data access object)
以上方法还是存在不足
- SQL语句固定,不能通过参数传入,通用性不好,需要进行改进,更方便执行增删改查
- 对于select操作,如果有返回值,返回值类型不能固定,需要使用泛型
- 将来的表很多,业务需求复杂,不可能只靠一个Java类完成
各司其职,每个DAO,操作对应的表
DAO
数据访问对象

浙公网安备 33010602011771号