宁愿辛苦一阵子,不要辛苦一辈子
小弟的目录
JDBC
什么是JDBC?
Java Database Connectivity:java连接数据库
JDBC是代表一组API,一个独立于特定数据库管理系统,通用的SQL数据库存取和操作的公共接口(一组API).
SUN公司为了使java代码可以跨数据库,既是指数据库换了,我们JDBC的代码不改变(或少改变)设计了一组公共的接口(标准),规定了所有操作数据库的代码,应使用哪些类型,哪些方法
这些操作数据库的具体代码由数据库厂商来实现,这些实现类,我们称之为数据库驱动,这就意味着,你要连接和操作数据库,就必须加载数据库的驱动程序
那么java代码就可以通过接口+驱动+标准的SQL语句实现java代码和各种数据库的连接和操作.
JDBC新手(实现最普通功能的JDBC)
操作步骤详解:
1.加载数据库驱动和注册驱动
有两种方式:
(1)是直接在源码库中引入,相当于绝对路径,但是是引入型的,将项目打包时,并不会打包驱动的jar文件(占得内存小,但是打包时,会丢失驱动)
(2)创建文件,导入型,在项目中创建目录并导入,这是相当于把jar驱动复制在了项目中,打包时会携带(不会丢失驱动,但是占的内存比较大)
注册驱动:
Class.forName("com.mysql.jdbc.Driver");
2.获取连接
url = "jdbc:mysql://localhost:3306/test";
主协议 协议 主机名 端口号 数据库名
user = "数据库账号";
password = "数据库密码";
Connection ct = DriverManger.getConnection(url,user,password);
3.执行sql语句:
sql = "insert into dept values(6,'张三',15)";
Statement st = ct.createStatement();
int len = st.executeUpdate(sql);
System.out.println(len>0?"添加成功":"添加失败");
如上是添加数据,凡是添加,删除,修改都是修改操作,都调用executeUpdate()方法,方法的参数是sql语句,返回值是len(int类型),凡是select语句都调用executeQuery()方法,遍历查询,返回值是一个set,要用ResultSet接受
4.关闭连接
st.close();
ct.close();
分开代码,各位客官可能不容易记忆和查看
整体代码:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
/**
*
* 1.注册驱动,加载驱动类到内存中,即内存中有驱动类对象
* 2.获取连接,即登录
* 3.执行sql
* (1)编写sql语句
* (2)创建Statement对象
* (3)用Statement来执行sql操作,并接受结果
* 4.关闭连接
*
* API:
* java.sql.Connection;接口,代表连接
* java.sql.DriverManager, 代表类,驱动管理类
* java.sql.Statement;
*
*协议,主机地址,端口号,路径(查询字串)
* 用java添加一个人到test1库中dept(id,name,money)表中
*/
public class dome1 {
//先导入驱动数据库驱动
public static void main(String[] args) throws Exception {
//1.注册驱动,加载驱动类到内存中,即内存中有驱动类对象
//Class.forName("org.git.mm.mysql.Driver");//旧版驱动注册方式
Class.forName("com.mysql.jdbc.Driver");//新版驱动注册方式
//2.获取连接
String url = "jdbc:mysql://localhost:3306/test1";
String user = "root";
String password = "123456";
//驱动已经加载了,现在用驱动管理类DriverManager
Connection connection = DriverManager.getConnection(url,user,password);
//3.执行sql语句
//3.1编写sql语句
String sql = "insert into dept values(6,'雷',15)";
//要把sql语句发给服务器端执行,并接受他返回的结果
//3.2创建Statement对象
Statement statement = connection.createStatement();
//3.3用Statement来执行sql操作,并接受结果
//凡是insert,delete,update语句都是更新数据库
//凡是select都是查询query
int len =statement.executeUpdate(sql);//返回一个整数,表示多少行收到影响
System.out.println(len>0?"添加成功":"添加失败");
//4.关闭连接
statement.close();
connection.close();
}
}
JDBC强者(对普通JDBC优化)
强者就是能一次干到多个敌人(sql语句),先讲个开胃菜,和优化无关,但是我没放到基础里面,怕让新手感到恐惧(其实我多想了,大家都是很强的)
关键的方法"摘"出来(有些方法,我感觉必须得对数据库事务有些了解,才能食用)
- commit()提交事务
- rollback()回滚事务
- setAutoCommit() 参数为false时为手动提交模式,为true时当然是自动提交sql代码模式
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author 雷雨
* @date 2020/4/22 16:44
*
* 用数据库来处理事务
* mysql中默认是自动提交事务的,执行一句,提交一句
*
* 需求
* java中JDBC操作实现:
* 1.在数据库中修改名字为小雷的名字修改为胡歌
* 2.在数据库中插入新的数据name为张翰,薪资为21
*
* 要求这是一个数据库事务的操作,
* 即要么都操作,要么都不操作
*
*/
public class TestTransAction {
public static void main(String[] args) {
Connection connection = null;
//1.注册驱动
try {
Class.forName("com.mysql.jdbc.Driver");
//2.创建连接(因为是数据库事务,所以我们要实现的是在执行多个sql语句时,保证是一个连接)
String url = "jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8";
String user = "root";
String password ="123456";
connection = DriverManager.getConnection(url, user, password);
connection.setAutoCommit(false);
updata(connection);
insert(connection);
connection.commit();
} catch (Exception e) {
System.out.println("失败");
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}finally {
try {
connection.setAutoCommit(true);
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void updata(Connection connection)throws Exception{
String sql = "UPDATE dept set name=? where name=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,"胡歌");
preparedStatement.setObject(2,"小白");
int len = preparedStatement.executeUpdate();
preparedStatement.close();
}
public static void insert(Connection connection)throws Exception{
String sql = "INSERT into dept values(NULL ,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,"张翰");
preparedStatement.setObject(2,21);
int len = preparedStatement.executeUpdate();
preparedStatement.close();
}
}
Statement中可能遇到的问题
1.在需要控制台输入多个参数来进行操作是,往往需要sql语句的拼接,而sql语句的拼接是很麻烦的,很容易出错.
2.在控制台输入参数作为sql语句执行(查询中可能遇到导出数据库的情况)的一部分时,可能造成sql语句的注入,也是由于引号问题造成的.造成***信息(关键)
3.sql拼接不支持blob等二进制类型(比如我们需要给数据库中存储一个照片,一般不把照片存入数据库,因为照片占用的内存很大,影响效率,但是的确存在的情况,这时我们的sql拼接是不支持二进制路劲插入到sql语句的操作,会导致sql语句错误)
这里对前面所说的在执行sql语句中可能出现的问题做一个实例
下面这个例子我们可以看到在数据库只有三列属性的时候,我们的sql语句的拼接,已经很麻了,很容易发生错误.(虽然这不是很关键)
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Scanner;
/**
*
* 描述Statement的第一类问题:sql拼接
*需求:在test数据库中,从java控制台中传入多个参数(id和money),来插入这个人的信息
* Statement中的第一个问题sql语句的拼接,虽然能够正常的运行,但是在sql语句的拼接过程
* 中因为引号很复杂,所以很容易发生错误
*/
public class question1 {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要插入的用户的id");
String id = scanner.nextLine();
System.out.println("请输入要插入的用户的姓名");
String name = scanner.nextLine();
System.out.println("请输入要插入的用于的money");
String money = scanner.nextLine();
//Register Database Driver
Class.forName("com.mysql.jdbc.Driver");
//Get A Database Connection
String url = "jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf8";
String user = "root";
String password = "123456";
Connection connection = DriverManager.getConnection(url, user, password);
//Execute sql statement
String sql = "insert into dept(id,name,money) values ('"+id+"','"+name+"','"+money+"')";
Statement statement = connection.createStatement();
int len = statement.executeUpdate(sql);
System.out.println(len>0?"添加成功":"添加失败");
//Close connection
statement.close();
connection.close();
scanner.close();
}
}
第二个实例:sql注入
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;
/**
* @author 雷雨
* @date 2020/4/21 23:07
*
* 需求:从键盘输入name,根据这个name查询,属于这个id的其他信息
*
*Statement中可能出现的第2个问题就是:sql语句的注入
*
*/
public class question2 {
public static void main(String[] args) throws Exception{
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要查询的id");
String name =scanner.nextLine();
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/test1";
String user = "root";
String password = "123456";
Connection connection = DriverManager.getConnection(url,user,password);
String sql = "select * from dept where name ='"+name+"'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()){
System.out.print(resultSet.getObject(1)+"\t");
System.out.print(resultSet.getObject(2)+"\t");
System.out.print(resultSet.getObject(3)+"\t");
}
resultSet.close();
statement.close();
connection.close();
scanner.close();
}
}
这个很重要,当我们输入一个正确的人名的时候,想必各位客官都知道能正常的输出,但是如果输入一个:胡歌' or '1'='1
(数据库中已有name为胡歌的),这时是会报异常呢,还是怎样呢?
各位可能看了截图就明白了:哦,原来说的sql注入是这样啊,其实我们在控制台的这个输入,就是用了sql语句写在字符串中的特性,将其引号,根据我们的想法拆解,最终得到了一个sql语句运行永远都是true,所以我们轻松的把数据库中的所有人的信息都盗取出来了.
听我解释的还有点懵的客官,返回代码中认真的分析引号,可以试着把我的输入带进去拆解,看是不是会有新的思维碰撞呢.
对于这些令人烦心的麻烦,我们接下来就要看怎么解决
用PreparedStatement来解决sql拼接sql注入sql无法解析二进制文件的问题
这一部分,我将操作的步骤都总结出来,因为比较简单,我不做多的赘述,实例代码中也有部分注释,会给大家一些提示和思考.
需要修改的部分
1.sql语句中用?
占位符来代替需要传入的字符(数据库中的属性列,或者二进制的路径)
2.用连接对象(connection)创建preparedStatement对象,并传入sql语句
3.perparedStatement对象调用setobject()方法传入属性列或二进制
4.调用执行sql语句的方法,这时不用再传入sql语句了
import java.sql.*;
import java.util.Scanner;
/**
*这里只列举一种,其他的解决方式是类似的,不做赘述
*/
public class resolve1 {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要插入的用户的id");
String id = scanner.nextLine();
System.out.println("请输入要插入的用户的姓名");
String name = scanner.nextLine();
System.out.println("请输入要插入的用于的money");
String money = scanner.nextLine();
//Register Database Driver
Class.forName("com.mysql.jdbc.Driver");
//Get A Database Connection
String url = "jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf8";
String user = "root";
String password = "123456";
Connection connection = DriverManager.getConnection(url, user, password);
//Execute sql statement
String sql = "insert into dept(id,name,money) values (?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,id);
preparedStatement.setObject(2,name);
preparedStatement.setObject(3,money);
int len = preparedStatement.executeUpdate();
System.out.println(len>0?"添加成功":"添加失败");
//Close connection
preparedStatement.close();
connection.close();
scanner.close();
}
}
优化后JDBC代码仍然有的缺陷
1.注册驱动,获取连接的代码我们重复了很多遍(加大了程序员的工作量和代码复用率很低)
2.每次我们都从mysql中获取连接
-
mysql是一个YCP/IP的网络程序,每次获取连接的成本很高(需要三次握手,四次挥手等)
-
每个客户端都有单独的线程来维护它的请求,这是会出现的问题:
(1)如果很多的客户端都同时去访问服务器,那么mysql的并发量增大,可能会挂,特别是遇到一些程序员,获取连接后不关闭,那么服务器的并发太高会挂
(2)每次高成本获取的链接只使用了一次,太奢侈
我们需要的是:一次获取,多次使用
JDBC大神(高级)
(1)解决问题一:我们把注册驱动,获取连接封装到一个工具类,不仅可以减少代码量,也为后期代码的重构提供了便利
(2)解决问题二:我们可以使用"数据库连接池"来解决
什么叫数据库连接池技术?
(1)先创建一个连接池pool,然后在池中先放一些对象,然后程序需要的使用,先去连接池pool中看有没有对象,如果有,就不用创建.
(2)我们还可以设置连接池的最大连接数量,如果池中所有连接都在使用的话,那么就让客户端"等待",这样虽然有等待的情况,但是比服务器"挂"了更好一点.
(3)在创建连接池时,可以先创建少量的连接,等用户连接数高时,再创建多个连接,直到最大连接数量为止.
(4)之前connection.close()相当于真的与服务器断开连接,而在连接池中connection.close的方法是把连接的对象返回给连接池.
总结一下使用数据库连接池的特点:
1.资源重用
由于数据库连接是重用的,避免了频繁的创建,释放连接引起的重大的性能开销,在减少了系统开销的基础了,还增加了系统运行的平稳性
2.更快的反映速度
数据库连接池在初始化中已经创建了少量的连接置于连接池中备用.此时连接的初始化工作均已完成,对于业务请求而言,直接利用现有的连接,而不用自己去创建连接,避免了在创建和释放连接过程中的时间开销,从而减少系统反应时间
3.新的资源分配手段
对于多应用同享一个数据库的系统而言,可在应用层通过连接池的配置,实现某一应用最大连接数的限制,避免某一应用独占所有的资源.
4.统一的资源管理,避免数据库连接泄露
在较为完善的数据库连接池中实现,可根据预先的占用超时的设定,强制收回被占用的资源,从而避免了常规数据库中的连接泄露(练级泄露简单来讲就是只连接,而不释放).
有些客官会问了如何使用数据库连接池?
使用数据库连接池(德鲁伊)
步骤
1.引入"德鲁伊"jar包
2.加一个配置文件,配置德鲁伊连接池的参数
数据库连接池的作用:管理连接,
所以我,们从这点出发我们需要配置的参数有:
主机名,端口号,用户名,密码,驱动类名
其他需要配置的参数:初始化连接数,最多连接数…
3.怎么写配置:
(1) 在src文件下(其实并不局限于,但是为了我们后期代码方便操作,否则会抛出java.lang.NullPointerException异常)创建一个.properties
的文件写入
url =jdbc:mysql://localhost:3306/test1?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf-8"
username=root
password=123456
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=20
maxWait=1000
filters=wall
4.创建连接池
5.创建一个方法:可以在数据库连接池中拿对象
哎呀,成堆的概念看的人头疼,对于程序员来讲,没有什么是实战更有用的学习方式了
于是,小二,上代码:
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
*
*
* 连接池的使用
*/
public class JDBCUtils {
private static DataSource ds;//这个的作用在后面,相当加载连接
private static ThreadLocal<Connection> local;
static {
//静态代码块可以用来初始化我们的静态变量
//(1)把druid.properties文件的数据加载到一个properties的对象中
try {
Properties properties = new Properties();
properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));//这里要传一个类的加载器的对象
ds = DruidDataSourceFactory.createDataSource(properties);
local = new ThreadLocal<>();
} catch (Exception e) {
e.printStackTrace();
}
}
// 这段代码可以使用,但是不能保证在同一线程中(客户端)能共享同一个连接对象
//后面的代码如果用到数据库事务的处理时,就发生问题,不能回滚等
// public static Connection getConnection() throws SQLException {
// return ds.getConnection();
// }
//这里修改为ThreadLocal来保存同一线程的共享变量:
public static Connection getConnection() throws SQLException{
//如果能够在local中能拿到一个连接对象,那么说明当前线程已经拿过了
Connection connection = local.get();
//如果不能获取连接对象,说明之前没拿过
if(connection == null){
connection=ds.getConnection();
local.set(connection);
}
return connection;
}
//提供一个关闭连接的方法:
public static void free(){
Connection connection =local.get();
if(connection!=null){
local.remove();
//还原数据库为自动提交模式,这样下次再拿到这个连接时,就默认是自动提交
try {
connection.setAutoCommit(true);
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Scanner;
/**
* 需求:
* 从键盘输入一个dept库的信息(id,name,money),存储到dept表中
*
*/
public class TestJdbcUtils {
public static void main(String[] args) throws Exception{
//1.键盘输入
Scanner scanner = new Scanner(System.in);
// System.out.println("请输入要插入的id");
// String id = scanner.nextLine();
System.out.println("请输入要插入的name");
String name = scanner.nextLine();
System.out.println("请输入要插入的money");
String money = scanner.nextLine();
//2.获取连接,这里我们使用的是我们自己写的工具类,不使用原来的方法
Connection connection = JDBCUtils.getConnection();
//3.编写sql语句
String sql ="insert into dept(name,money) values(?,?)";
//4.创建PreparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//5.set?的值
// preparedStatement.setObject(1,id);
preparedStatement.setObject(1,name);
preparedStatement.setObject(2,money);
//6.执行更新
int len = preparedStatement.executeUpdate();
System.out.println(len>0?"添加成功":"添加失败");
//7.关闭
preparedStatement.close();
JDBCUtils.free();
scanner.close();
}
}
还剩一个封装自己的BasicDAO,但是夜深了,肝不动了,明天还有网课,我太难了,今天的文章就写到这吧,下次专门写一篇博客吧.最后再把今天的毒鸡汤和各位共食:宁愿辛苦一阵子,不要辛苦一辈子,一起努力吧.