JDBC在mysql8.x上的使用
-
mysql8.0和之前版本的区别,首先驱动换了,不是
com.mysql.jdbc.Driver
而是com.mysql.cj.jdbc.Driver
5.5版本的注册
Class.forName(com.mysql.jdbc.Driver());
8.x版本的注册
Calss.forName(com.mysql.cj.jdbc.Driver());
-
mysql8.x是不需要建立ssl连接的,需要显示关闭。需要配置
serverTimezone
属性设置时区Url="jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8&autoReconnect=true"; //连接地址+ssl连接关闭+时区+字符集为utf-8+数据库自动连接
UTC代表的是全球标准时间 ,但是我们使用的时间是北京时区也就是东八区,领先UTC八个小时。时区使用中国标准时间,也是就serverTimezone=Asia/Shanghai。
用jdbc连接数据库
package com.lxc.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.mysql.cj.jdbc.Driver;
public class JDBCDemo1 {
public static void main(String[] args) {
Connection conn=null;
Statement stmt=null;
ResultSet resultSet=null;
try {
//1、加载驱动
//DriverManager.registerDriver(new Driver());//会进行两次注册驱动(加载Driver类一次,执行这条语句又一次)
//加载Driver类,Driver类中的静态代码块中含有DriverManager.registerDriver(new Driver());代码,只要加载Driver类就会注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2、获得连接
//三个参数1、localhost是主机名,3306是端口号,jdbctest是数据库 2、用户名 3、密码
conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest?useSSL=false&serverTimezone=Asia/Shanghai","root","123456");
//3、创建执行SQL语句的对象,并执行SQL
//3.1创建执行sql语句的对象
stmt=conn.createStatement();
String sql="select * from user";
//3.2、执行sql语句获得结果集
resultSet=stmt.executeQuery(sql);
//3.3、读取结果集中的数据
while(resultSet.next()) {
int id=resultSet.getInt("uid");
String username=resultSet.getString("username");
String password=resultSet.getString("password");
String name=resultSet.getString("name");
System.out.println(id+" "+username+" "+password+" "+name);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//4、释放资源
if(resultSet!=null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet=null;
}
if(stmt!=null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt=null;
}
if(conn!=null) {
try {
//这里虽然关闭了资源,但不会马上被垃圾回收机制回收
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
//手动置为空,让这个连接资源马上被回收
conn=null;
}
}
}
}
- 释放资源必须放在
finally
中,因为放在try
块中,如果在前面发生了异常,那么这些资源将不会被正常回收。但是放在finally
块中,无论是否会有异常,都会执行这个块中的代码,进行资源的释放。 - 特别是
Connection
对象,它是非常稀有的资源(数据库中规定了最大的连接数),用完后必须马上释放,如果Connection
不能及时、正确的关闭,极易导致系统宕机。Connection
的使用原则是尽量晚创建,尽量早的释放。
DriverManager :驱动管理类
主要作用:
-
注册驱动
- 实际开发中注册驱动会使用如下的方式:
Class.forName("com.mysql.cj.jdbc.Driver");
-
获得连接
Connection conn=DriverManager.getConnection(String url,String username,String password)
url写法: jdbc:mysql://localhost:3306/jdbc?.....
- jdbc :协议
- mysql:子协议
- localhost :主机名
- 3306:端口号
Connection:获得连接对象(Connection是接口)
主要作用:
- 创建执行SQL语句的对象(调用下面的方法获得不同的执行SQL语句的对象)
Statement createStatement()
:获得执行SQL语句的对象,有SQL注入的漏洞存在。PreparedStatement prepareStatement(String sql)
:这个接口继承了Statement
,预编译SQL语句,解决SQL注入漏洞的问题。CallableStatement prepareCall(String sql)
:执行SQL中的存储过程。
- 进行事务的管理
setAutoCommit(boolean autoCommit)
:设置事务是否自动提交,true为自动,false为禁止自动commit()
:事务提交rollback()
:事务回滚
Statement(接口):执行SQL语句
主要作用:
-
执行SQL语句
boolean execute(String sql)
:执行SQL,执行select
语句返回true,其它返回false。ResultSet executeQuery(String sql)
:执行SQL中的select
语句int executeUpdate(String sql)
:执行SQL中的insert/update/delete
语句,返回影响的行数
-
执行批处理操作
addBatch(String sql)
:添加到批处理executeBatch()
:执行批处理,返回一个数组,保存着每执行一次sql语句影响的行数clearBatch()
:清空批处理
PreparedStatement
的这些函数在传参等方面略有不同
//用PreparedStatement的批处理执行多条delete操作
package com.lxc.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCDemo3 {
public static void main(String[] args) {
String driver="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://localhost:3306/jdbctest?useSSL=false&serverTimezone=Asia/Shanghai";
String username="root";
String password="123456";
Connection conn=null;
PreparedStatement pstmt=null;
try {
Class.forName(driver);
conn=DriverManager.getConnection(url,username,password);
String sql="delete from user where username=?";
pstmt=conn.prepareStatement(sql);
//设置为不自动提交事务,自动提交不能实现真正的批处理
conn.setAutoCommit(false);
for(int i=1;i<10;i++) {
pstmt.setString(1, "lewis"+String.valueOf(i));
pstmt.addBatch();
}
int[] cnt=pstmt.executeBatch();//返回每次操作影响了几行的数组
conn.commit();//手动提交事务
System.out.println("总共执行了"+cnt.length+"次删除操作");
for(int n:cnt) {
System.out.println(n);
}
conn.setAutoCommit(true);//恢复自动提交事务
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(pstmt!=null) {
try {
pstmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
pstmt=null;
}
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
conn=null;
}
}
}
}
ResultSet:结果集
结果集:其实就是查询语句(select)查询结果的封装。
主要作用:
- 通过结果集获取到查询结果
next()
:针对不同类型的数据可以使用getXXX()
获取数据,通用的获取方法:getObject()
提取工具类
properties
文件:
- 以键值对的形式保存信息,存储格式
键=值
- 我这里的文件是放在
ClassPath
根目录下的
driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbctest?useSSL=false&serverTimezone=Asia/Shanghai
username=root
password=123
工具类:
- 一些可能变化的信息从属性文件中读取,如果改变,只需改变属性文件就行
package com.lxc.jdbc.utils;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtils {
private static String driverClass;
private static String url;
private static String username;
private static String password;
//静态代码块,在类加载的时候执行
static {
//加载属性文件并解析
Properties properties=new Properties();
//使用类加载器的方式获取属性文件的输入流
/*不能使用FileInputStream()的方式获得文件的输入流,这种方法只能用在java程序中,这种方式如果在web项目中程序会到tomcat/bin目录下加载该属性文件*/
//jdbc.properties放在classpath根目录下,下面是到classpath根目录下读取jdbc.properties文件
//同样也可以使用JDBCUtils.class.getResourceAsStream("/jdbc.properties")获得该文件输入流
InputStream is=JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
//解析.properties文件中的键值对,存进HashTable中,供后面的getProperty(Stirng key)使用
properties.load(is);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//从属性文件中获取键值对中的值
//本质是通过键获取HashTable中的值
driverClass=properties.getProperty("driverClass");
url=properties.getProperty("url");
username=properties.getProperty("username");
password=properties.getProperty("password");
}
/**
* 注册驱动的方法
*/
public static void loadDriver() throws ClassNotFoundException {
Class.forName(driverClass);
}
/**
* 获得连接的方法
* @throws ClassNotFoundException
* @throws SQLException
*/
public static Connection getConnection() throws ClassNotFoundException, SQLException {
JDBCUtils.loadDriver();
return DriverManager.getConnection(url,username,password);
}
/**
* 释放资源
*/
public static void release(Connection conn,PreparedStatement pstmt) {
if(pstmt!=null) {
try {
pstmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
pstmt=null;
}
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
conn=null;
}
}
public static void release(Connection conn,PreparedStatement pstmt,ResultSet rs) {
if(rs!=null) {
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
rs=null;
}
if(pstmt!=null) {
try {
pstmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
pstmt=null;
}
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
conn=null;
}
}
}
类名.class.getClassLoader.getResourceAsStream(path)
中路径问题path
不能以“/”开头。path
是从ClassPath
(项目中的src
,在Eclipse
项目文件中的bin目录)根下获取。
[关于Class.getResource和ClassLoader.getResource的路径问题]
Class.getResource和ClassLoader.getResource的区别分析
使用工具类:
package com.lxc.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import com.lxc.jdbc.utils.JDBCUtils;
public class JDBCDemo3 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement pstmt=null;
try {
//使用工具类中的方法获得连接对象
conn=JDBCUtils.getConnection();
String sql="delete from user where username=?";
pstmt=conn.prepareStatement(sql);
conn.setAutoCommit(false);
for(int i=1;i<10;i++) {
pstmt.setString(1, "lewis"+String.valueOf(i));
pstmt.addBatch();
}
int[] cnt=pstmt.executeBatch();
conn.commit();
System.out.println("总共执行了"+cnt.length+"次删除操作");
for(int n:cnt) {
System.out.println(n);
}
conn.setAutoCommit(true);
} catch (Exception e) {
e.printStackTrace();
}finally {
//使用工具类中的方法释放资源
JDBCUtils.release(conn,pstmt);
}
}
}
SQL注入漏洞的解决
产生的原因
public static boolean login(String username,String password){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
boolean flag = false;
try{
conn = JDBCUtils.getConnection();
stmt = conn.createStatement();
String sql = "select * from user where username = '"+username+"' and password = '"+password+"'";
rs = stmt.executeQuery(sql);
if(rs.next()){
flag = true;
}else{
flag = false;
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, stmt, conn);
}
return flag;
}
上面是验证登录的代码,如果我们在传入参数的时候,在参数中包含一些SQL的关键字(and\or\注释)。都会被拼接到SQL语句中,成为SQL语句的一部分。比如login("aaa or '1==1'",password)
,无论密码是什么都会登录成功,因为传入参数后SQL语句变成了select * from user where username='aaa' or '1==1' and password='asdfj';
,只要用户名对了就能登录成功。
SQL注入漏洞的解决
PreparedStatement
能很好的解决这个问题PreparedStatement
是Statement
的子接口PreparedStatement
相对于Statement
而言PreparedStatement
可以避免SQL注入的问题,因为在PreparedStatement
中的SQL语句中会使用占位符,传入参数的时候,尽管传入一些含有SQL关键字的字符串,但是SQL语句并不会识别这些关键字,而仅仅是当作普通的字符串来处理。(SQL语句的格式已固定)Statement
会使数据库频繁编译SQL,可能会造成数据库缓冲区的溢出。而PreparedStatement
可对SQL语句进行预编译(只要是同一种格式的SQL语句,只编译一次,可使用多次),从而提高数据库的执行效率。- 并且
PreparedStatement
对于sql中的参数,允许使用占位符的形式进行替换,简化SQL语句的编写。
public static boolean login2(String username,String password){
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
boolean flag = false;
try{
conn = JDBCUtils.getConnection();
String sql = "select * from user where username = ? and password = ?";
pstmt = conn.prepareStatement(sql);
//第一个参数代表第几个占位符,从1开始
pstmt.setString(1, username);
pstmt.setString(2, password);
rs = pstmt.executeQuery();
if(rs.next()){
flag = true;
}else{
flag = false;
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, pstmt, conn);
}
return flag;
}
JDBC连接池—C3P0
-
C3P0
是开源的JDBC连接池 -
连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要他们的线程使用。
-
原来没有连接池的缺点
没有连接池的时候,这些连接都是需要的时候创建,用完后马上销毁,所以会导致数据库一直在创建和销毁连接。 -
有连接池的优点
在数据库连接池中存在着一定数量的连接,当需要使用的时候就取出来使用,等到用完后不会销毁,而是归还到连接池中,这样就避免了大量的创建连接耗费资源等问题。[外链图片转存失败(img-FGRZICiR-1565245636240)(assets/1565192749899.png)]
C3P0的使用
- 下载后需要把``c3p0-0.9.5.4.jar
和依赖包
mchange-commons-java-0.2.15.jar两个包导入到工程的
ClassPath`中
配置c3p0-config.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="mysql">
<!-- 配置数据库用户名 -->
<property name="user">root</property>
<!-- 配置数据库密码 -->
<property name="password"></property>
<!-- 配置数据库链接地址 -->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/cdcol?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai</property>
<!-- 配置数据库驱动 -->
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<!-- 数据库连接池一次性向数据库要多少个连接对象 -->
<property name="acquireIncrement">20</property>
<!-- 初始化连接数 -->
<property name="initialPoolSize">10</property>
<!-- 最小连接数 -->
<property name="minPoolSize">5</property>
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize">30</property>
<!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:0 -->
<property name="maxStatements">0</property>
<!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0 -->
<property name="maxStatementsPerConnection">0</property>
<!--c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能 通过多线程实现多个操作同时被执行。Default:3 -->
<property name="numHelperThreads">3</property>
<!--用户修改系统配置参数执行前最多等待300秒。Default: 300 -->
<property name="propertyCycle">3</property>
<!-- 获取连接超时设置 默认是一直等待单位毫秒 -->
<property name="checkoutTimeout">1000</property>
<!--每多少秒检查所有连接池中的空闲连接。Default: 0 -->
<property name="idleConnectionTestPeriod">3</property>
<!--最大空闲时间,多少秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime">10</property>
<!--配置连接的生存时间,超过这个时间的连接将由连接池自动断开丢弃掉。当然正在使用的连接不会马上断开,而是等待它close再断开。配置为0的时候则不会对连接的生存时间进行限制。 -->
<property name="maxIdleTimeExcessConnections">5</property>
<!--两次连接中间隔时间,单位毫秒。Default: 1000 -->
<property name="acquireRetryDelay">1000</property>
<!--c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试。如果定义了这个参数那么属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操作,它将只供c3p0测试使用。Default: null -->
<property name="automaticTestTable">Test</property>
<!-- 获取connnection时测试是否有效 -->
<property name="testConnectionOnCheckin">true</property>
</named-config>
</c3p0-config>
-
在xml文件中可以配置多个数据库源连接信息,比如可以是mysql、oracle
可以使用c3p0-config.xml文件配置连接信息,使用xml作为配置信息的话,
comboPoolDataSource
还可以接受一个String参数,这个参数的名称是在c3p0-config.xml文件中配置的,也可以使用无参的默认配置。这样就能连接不同的数据库、连接不同厂商的数据库public ComboPooledDataSource() //无参构造使用默认配置(使用xml中default‐config标签中对应的参数) public ComboPooledDataSource(String configName) //有参构造使用命名配置(configName:xml中配置的名称,使用xml中named‐config标签中对应的参数) //例如在xml中:<named-config name="oracle">各种配置信息</named-config>
我的c3p0-config.xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <!--默认配置,无参时使用这个--> <default-config> <!--连接参数 --> <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <!--使用&对&进行转义--> <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbctest?useSSL=false&serverTimezone=Asia/Shanghai</property> <property name="user">root</property> <property name="password">123456</property> <!-- 连接池参数 --> <property name="initialPoolSize">5</property> <property name="maxPoolSize">20</property> </default-config> <!--命名配置,配置mysql数据库,传入"mysql"参数使用这个 --> <named-config name="mysql"> <!-- 配置数据库用户名 --> <property name="user">root</property> <!-- 配置数据库密码 --> <property name="password">123456</property> <!-- 配置数据库链接地址 --> <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbctest?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai</property> <!-- 配置数据库驱动 --> <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <!-- 数据库连接池一次性向数据库要多少个连接对象 --> <property name="acquireIncrement">20</property> <!-- 初始化连接数 --> <property name="initialPoolSize">10</property> <!-- 最小连接数 --> <property name="minPoolSize">5</property> <!--连接池中保留的最大连接数。Default: 15 --> <property name="maxPoolSize">30</property> </named-config> <!-- 配置ORACLE数据库 --> <named-config name="oracle">各种配置信息</named-config> </c3p0-config>
-
配置文件的连接方式
-
C3P0默认会在
classpath(src)
根目录读取配置文件c3p0-config.xml
,不需要任何的设置。所以我们最好把配置文件放在classpath
根目录下,而且文件名为c3p0-config.xml
(不要更改)。 -
也可以把
c3p0-config.xml
放到自己想放的位置,但必须在加载程序的时候进行设置System.setProperties(“ com.mchange.v2.c3p0.cfg.xml”,”config/c3p0-config.xml”);
-
工具类:
package com.lxc.jdbc.utils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0Utils {
private static ComboPooledDataSource dataSource;
static {
dataSource=new ComboPooledDataSource();
//也可以使用命名配置的数据库
//dataSource=new ComboPooledDataSource("mysql");
}
/**
* 获得连接
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
/**
* 释放ResultSet和PreparedStatement资源,并把Connection归还到连接池中
* @param stmt
* @param conn
*/
public static void release(Statement stmt,Connection conn){
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
public static void release(ResultSet rs,Statement stmt,Connection conn){
if(rs!= null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
操作
package com.lxc.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.lxc.jdbc.utils.C3P0Utils;
public class C3P0Test {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement pstmt=null;
ResultSet rs=null;
try {
//利用工具类获得连接
conn=C3P0Utils.getConnection();
String sql="select * from user";
pstmt=conn.prepareStatement(sql);
rs=pstmt.executeQuery();
while(rs.next()) {
System.out.println(rs.getInt("uid")+" "+rs.getString("username")+" "+rs.getString("password")+" "+rs.getString("name"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//以前调用conn.close()是释放资源,但是C3P0中对方法进行了升级,把销毁连接改成了把连接归还到连接池中
C3P0Utils.release(rs, pstmt,conn);
}
}
}