- java.sql.Connection 数据库连接
- 我们对数据库的一切操作都是从获取Connection开始
- 获取数据库连接的四个参数:
1.数据库的地址 url
语法:jdbc:子协议:厂商内容
MySQl的格式:jdbc:mysql://主机名:端口号/数据库名字
例子:jdbc:mysql://localhost:3306/test
2.用户名 user 连接数据库使用的用户名
3.密码 password 数据库的密码
4.数据库驱动全类名 driverClass
- 基本步骤:
1.导入数据库驱动的jar包
mysql-connector-java-5.1.37-bin.jar
2.准备四个参数
- url
- user
- password
- driverClass
3.加载数据库驱动
Class.forName(driverClass)
4.通过DriverManager来获取数据库连接
static Connection getConnection(String url, String user, String password)
- 问题:
- 在JDBC的代码中有如下一行代码:
Class.forName("com.mysql.jdbc.Driver");
- 我们发现这行代码和其他的代码没有上下文关系,就是不写他编译也不会出错。
而且,这行代码不写也好使,这是为什么呢?
- Class.forName() 主要是将一个类加载进的虚拟机。
- 通过观察mysql的Driver的源码,发现类中有一个静态代码块:
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
- 静态代码块会在类被加载进虚拟机时就会执行,也就是当我们调用 Class.forName("com.mysql.jdbc.Driver");
以后,该静态代码块会立即执行。
而静态代码块中,有如下代码:
java.sql.DriverManager.registerDriver(new Driver());
这个代码会将数据库驱动注册进DriverManager中。
- 但是我们这个东西不写也好使,因为在JDBC4以后,程序会自动加载数据库驱动。
- 虽然是这样,但是我们要求这行代码必须写。尤其是在web工程,更是必须写。
- 核心类:
- java.sql.DriverManager
- 数据库驱动的管理器,负责加载数据库的驱动获取数据库连接
- static Connection getConnection(String url, String user, String password)
- getConnection方法用来通过url地址,用户名,密码等参数来获取数据库连接的
- java.sql.Connection
- 数据库连接
- Statement createStatement()
- 创建一个Statement对象,通过Statement对象来执行SQL语句
- java.sql.Statement
- SQL语句的执行器
- boolean execute(String sql)
- 执行一条SQL语句,并返回一个布尔值,执行成功返回true,执行失败返回false。用的不多
- ResultSet executeQuery(String sql)
- 执行查询的SQL语句,并返回一个结果集
- int executeUpdate(String sql)
- 执行修改数据的SQL语句(增删改),并返回受影响的行数
- java.sql.ResultSet
- 查询到的数据的结果集,我们通过JDBC查询数据库获得的数据,都封装在ResultSet中
- boolean next()
- 控制光标向下移动一行,如果光标当前位置是afterLast则返回false,告诉你没数据了,就别读了。
如果光标移动以后,没有在afterLast则返回true,可以读取数据。
- 在ResultSet有很多getXxx(int),比如getString(),getInt(),getByte()。
通过这些方法可以读取当前行的数据,它们需要一个int值作为参数,
int指的是读取数据的列数。
列数是从1开始的。
- 在ResultSet中还有很多getXxx(String),它和上边的方法的作用一致,
只不过它们需要的都是String类型的参数,参数代表的是当前的列名,
比如:我们要获取id的值
getInt("id")
要获取name的值
getString("name")
注意:如果查询的SQL使用了别名,则列名以别名为准。
3)数据的增删改
//创建一个SQL执行器
Statement stat = conn.createStatement();
//创建一个SQL语句
String sql = "INSERT INTO t_stu(`name` , age) VALUES('沙僧',28)";
//执行SQL语句
//executeUpdate用来执行一条修改SQL的语句
//它需要一个String类型sql作为参数,并会返回一个int型的值,该值表示SQL语句执行以后影响到的行数
int count = stat.executeUpdate(sql);
4)数据的查询
//创建Statement对象
Statement stmt = conn.createStatement();
//创建一个SQL语句
String sql = "SELECT id, name sname, age FROM t_stu WHERE id=2";
//执行查询
ResultSet rs = stmt.executeQuery(sql);
//控制光标下移一行
//如果当前行有数据,则读取
if(rs.next()){
//获取id,name,age
int id = rs.getInt("id");
String name = rs.getString("sname");
int age = rs.getInt("age");
System.out.println(id+"--"+name+"--"+age);
}
> 查询操作和修改的主要不同的是,查询使用executeQuery(),
它会返回ResultSet结果集,我们需要对结果集进行读取。
> 当我们只需要读取一个数据时,用if。
当需要读取全部数据时,用while
> 代码的规范:
- Connection、Statement、ResultSet,这些资源都是需要和数据建立连接的
这些资源我们并不是总需要使用,当我们不适用这些资源,需要将这些资源关闭。
- 关闭资源顺序:
从后往前关:
先关 ResultSet
在关 Statement
最后关 Connection
- 示例代码:
//定义三个变量
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
}catch(Exception e){
e.printStackTrace();
}finally{
if(rs!=null){
//关闭ResulSet
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2.JDBC
1) SQL注入
> 目前我们的使用的是Statement来执行SQL语句
而我们传递的参数时通过拼接字符串的形式添加进SQL语句
> "SELECT * FROM t_user WHERE username='"+username+"' AND password='"+password+"'"
> 这种形式如果正常用户的访问,问题不大,但是一旦出现恶意的用户,
他如果传递了这样一组参数 用户名 : a' OR 'a'='a 密码:a' OR 'a'='a
> 这两个参数如果拼接进SQL语句,SQL语句会变成如下的状态:
"SELECT * FROM t_user WHERE username='a' OR 'a'='a' AND password='a' OR 'a'='a'"
> 通过这种特殊的参数,导致我们SQL语句的语义完全改变,这样即使用户名和密码不正确也可以登录
这样就对我们的网站带来很大的安全隐患
> 解决:
1.在用户输入时进行验证,验证用户名中是否包含特殊字符 ' " 等等
2.如果我们一直使用Statement则永远都会有SQL注入的隐患,所以最佳解决方案是不使用Statement
而是使用PreparedStatement
2) PreparedStatement
> java.sql.PreparedStatement --> 预编译的Statement
> PreparedStatement是Statement的子接口,使用方式和Statement类似,但是它在Statement做了一些扩展。
> 获取PreparedStatement是通过Connection的prepareStatement()的方法获取的,这个方法需要传一个SQL语句,
当我们通过SQL语句获取一个PreparedStatement时,JDBC会将SQL语句先发送给MySQL,
让我们的数据库对SQL语句进行预编译操作。
> 使用PreparedStatement的好处:
1.PreparedStatement使用占位符来替换SQL语句中的变量,更方便编写和阅读,维护起来更加的简单。
2.MySQL在执行一条SQL语句时,要分两个步骤:
1) 编译SQL语句,检查SQL语句是否有语法错误
2) 执行SQL语句
使用Statement时,每一次都需要进行这两个步骤,而MySQL可以对已经编译过的语句进行缓存,
使用PreparedStatement,执行一次以后,会对SQL语句进行缓存,下次再次执行时效率更高。
这个优点体现的不明显。
3.使用PreparedStatement时SQL语句会交给MySQL进行预编译,预编译的过程MySQL会将SQL语句,
转换为一个类似于Java中方法的东西,而我们的那些填充的占位符,会以方法的参数的形式发送给MySQL
这样即使传递在奇怪的参数,也不能改变SQL语义,所以它有效的避免了SQL注入的问题。
> 所以我们实际的使用中绝对不能使用Statement而是使用PreparedStatement
3) 大数据
- 比较大的数据
- 大数据主要分两种:
1.大文本数据
2.大的字节数据
- mysql对文件的大小有限制,也就是超过一定的大小以后,文件将不能插入进数据库,插入时会抛出异常
- 我们可以修改mysql对文件的大小的限制,修改my.ini文件
在my.ini文件中添加如下配置
max_allowed_packet = 10M
4) 批处理(batch)
- 批处理指的是一次操作中执行多条SQL语句
- 批处理相比于一次一次执行效率会提高很多
- 批处理主要是分两步:
1.将要执行的SQL语句保存
2.执行SQL语句
- Statement和PreparedStatement都支持批处理操作,这里我们只需要掌握PreparedStatement的批处理方式:
- 方法:
void addBatch()
- 将要执行的SQL先保存起来,先不执行
- 这个方法在设置完所有的占位符之后调用
int[] executeBatch()
- 这个方法用来执行SQL语句,这个方法会将批处理中所有SQL语句执行
- mysql默认批处理是关闭的,所以我们还需要去打开mysql的批处理:
rewriteBatchedStatements=true
我们需要将以上的参数添加到mysql的url地址中
- 注意:低版本的mysql-jdbc驱动也不支持批处理
5) 事务(Transaction)
- 在开发中我们的一个业务往往需要同时操作多个表,这些操作往往是不可分割,业务中的对数据库的多次操作,
要么同时成功,要么全都失败。
- 注意:我们在同一个事务中使用的数据库连接(Connection)必须是同一个。
- 事务的特性(ACID):
原子性(atomicity)
一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency)
事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)
一个事务的执行不能被其他事务干扰。
即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)
持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。
接下来的其他操作或故障不应该对其有任何影响。
- 操作事务的基本步骤:
1.开启事务
- 开启事务以后,我们只后的所有操作将都会在同一个事务当中
2.操作数据库
- 开启事务以后再去操作数据库,所有操作将不会直接提交到数据库中
3.提交事务
- 将修改应用到数据库
4.回滚事务
- 数据库操作过程中出现异常了,回滚事务,回滚事务以后,数据库变成开启事务之前的状态
- mysql中的事务控制
#开启事务
START TRANSACTION
#回滚事务
ROLLBACK
#提交事务
COMMIT
- JDBC中的事务主要通过Connection对象来控制的
1.开启事务
void setAutoCommit(boolean autoCommit) throws SQLException;
- 设置事务是否自动提交,默认是自动提交
- 设置事务手动提交
conn.setAutoCommit(false);
2.提交事务
void commit() throws SQLException;
- 提交事务
conn.commit()
3.回滚事务
void rollback() throws SQLException;
- 回滚事务
conn.rollback()
- 事务控制的格式:
//创建一个Connection
Connection conn = null;
try{
//获取Connection
conn = JDBCUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
//对数据库进行操作
//操作成功,提交事务
conn.commit();
}catch(Exception e){
e.printStackTrace();
//回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally{
JDBCUtils.close(conn, null, null);
}
6) 数据库连接池
> 数据库连接池就是存放数据库连接(Connection)的集合
> 我们获取一个数据库连接是一个相对很麻烦的过程,
如果我们获取一个数据库连接,使用一次以后就给它关闭了
下一次再去使用的时候就要重新创建一个新的数据库连接。
> 所以我们提出了一个数据库连接池的概念,数据库连接池放的都是数据库连接(Connection)
我们在去使用数据库连接时候,不用再去重新创建数据库连接,而是直接从池中获取,
使用完的数据库连接,也不是直接销毁,而是要放回到连接池。
> 数据库连接池的常见的属性:
初始连接数量:数据连接池创建以后,保存数据库连接的数量
最小空闲连接数:数据库连接池最少得未使用的数据库连接的数量
最大空闲连接数:数据库连接池最大闲置连接数,当闲置连接数满了以后,将不会有其他连接进入池
每次增加连接数:当数据库连接都被占用以后,一次性增加的数据库连接的个数
最大连接数:数据库连接池的最大容量,当最大连接数饱和了,则不再创建新的数据库连接
最大等待时间:当数据库连接池饱和以后,等待获取数据库连接的时间
> 常见的数据库连接池
- 所有的数据库连接池都需要实现DataSource,当使用数据库连接池时,我们便不再需要使用DriverManger获取数据库连接
而是使用DataSource。
- Connection getConnection()
- 从数据库连接池中获取数据库连接对象
1.DBCP
- DBCP是Apache出品的一款数据库连接
- DBCP依赖于commons-pool
- 使用DBCP需要导入两个jar包:
commons-dbcp-1.4.jar
commons-pool-1.5.5.jar
- 当我们通过数据库连接池获取数据库连接以后,我们所获取到数据库连接已经不是我们熟悉的那个Connection
数据库连接池对Connection对象进行了包装,它修改Connection的close()方法,
再去调用close()数据库连接将不会真的关闭,而是要放回到数据库连接池中,供其他线程使用。
- 核心类:
BasicDataSourceFactory
2.C3P0(重点)
- C3P0使用的是XML作为配置文件
- 使用c3p0需要导入一个jar包:
c3p0-0.9.1.2.jar
- 导入c3p0的配置文件:
1.配置文件的名字:c3p0-cofig.xml
2.配置文件要求放到类路径下(src)
- 核心类:
ComboPooledDataSource
- 注意:
DataSource就相当于池子,我们的数据库连接都是从DataSource中获取的,
如果程序中有多个DataSource的实例,那么我们说你还不如不用数据库连接池。
所以我们的DataSource在项目中应该只有一个实例。
转载请注明出处!
http://www.cnblogs.com/libingbin/
感谢您的阅读。如果文章对您有用,那么请轻轻点个赞,以资鼓励。