JDBC学习
这里写目录标题
一级目录
二级目录
三级目录
一、JDBC开发的六个步骤
* jdbc编程的六个步骤
* 0、准备工作:导入数据库驱动jar包
* a、在工程文件中新建一个lib文件夹,把驱动jar复制到里面去
* b、将jar包家在打牌classpath下,右击jar-> add as library
* 1、注册数据库驱动
* 2、获取数据库连接 Connection对象
* 3、获取数据库库操对象 Statement对象
* 4、通过数据库操作对象执行SQL语句
* 5、返回结果、处理结果
* 6、释放资源
- driver: com.mysql.jdbc.Driver–>这时候连接桥
- url: jdbc:mysql://localhost:3306/jdbc_learn -->其中jdbc:mysql是协议, locaohost表示主机号,也可以写成127.0.0.1, 3306是端口号, 后面接上你需要连接的数据库
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//1、注册数据库驱动
//方式1:不推荐使用,源码Driver加载的时候也是建立了,,所以就多余了
/*
Driver driver = new Driver();
DriverManager.registerDriver(driver);*/
//方式2:
Class.forName("com.mysql.jdbc.Driver"); //只是触发类加载,类加载静态代码块就会执行,那么数据库驱动就会被注册,看Driver的源码
//2、获取数据库连接的Connection对象
String url = "jdbc:mysql://localhost:3306/jdbc_learn";
String username = "root";
String password = "975806";
Connection connection = DriverManager.getConnection(url, username, password);
System.out.println(connection);
//3、获取数据库操作对象 Statement
Statement statement = connection.createStatement();
//4、操作执行sql语句
String sql = "insert into user1(u_name,u_password,u_phone,u_email) values (\"yangkuang\",\"123456\", \"13476613854\",\"2242975806@qq.com\")";
int i = statement.executeUpdate(sql);
//5、返回结果,处理结果
System.out.println(i);
//6、释放资源
statement.close();
connection.close();
}
二、SQL注入问题(Statement与preparement)
使用Statement进行sql语句执行的时候,有可能会发生sql注入问题,什么是sql注入问题呢,看看下面的代码例子就知道了
1、Statement 的sql注入问题
public static void main(String[] args) throws SQLException, ClassNotFoundException {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String uName = sc.nextLine();
System.out.println("请输入用户密码:");
String uPassword = sc.nextLine();
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/jdbc_learn";
String username = "root";
String password = "975806";
Connection connection = DriverManager.getConnection(url, username, password);
Statement statement = connection.createStatement();
String sql = "select * from tb_user where username = '" + uName + "' and password = '"+ uPassword +"' ";
System.out.println(sql);
ResultSet resultSet = statement.executeQuery(sql);
if(resultSet.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
resultSet.close();
statement.close();
connection.close();
}
上面是数据库的表格,还有执行查询的结果,可以发现,使用Statement的时候,13行代码运行的时候,输入的用户名被转义了,导致了1=1变成了查询语句的一部分,这样子,不管用户是否存在,都显示查询成功。 |
2、使用PrepareStatement解决sql注入问题
public static void main(String[] args) throws SQLException, ClassNotFoundException {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String uName = sc.nextLine();
System.out.println("请输入用户密码:");
String uPassword = sc.nextLine();
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/jdbc_learn";
String username = "root";
String password = "975806";
Connection connection = DriverManager.getConnection(url, username, password);
/**
下面使用了prepareStatement,需要注意的是,使用过程中,我们直接事用?表示占位,表示接下来要传入sql语句的变量,
然后需要的就是设置占位符的值
*/
String sql = "select * from tb_user where username = ? and password = ? ";//?号表示占位符
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, uName);//设置占位符的值,1表示第一个占位符
preparedStatement.setString(2,uPassword);//2表示第二个占位符, uPassword表示值
System.out.println(preparedStatement);
ResultSet resultSet = preparedStatement.executeQuery();
if(resultSet.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
resultSet.close();
preparedStatement.close();
connection.close();
}
观察下面的运行结果,我们发现运行时,sql把整个名字传到占位符里面去,也就是说,名字不会被分开了,1=1自然也就不成立了,所以,数据库中没有存在该用户,所以就会登陆失败!!! |
---|
三、ORM对象关系映射
关系映射就是建立一个属性和数据库表格一样的实体类,也就是说,这个实体类就是表格的映射,我们可以通过实体类将数据插入表格,也可以将表格中查询到的数据赋值给实体类的属性,再打印出来
四、JDBC工具类
我们知道,JDBC开发一共需要6个步骤,每开启一个事务都需要,代码量就会显得相对多,而且操作不方便,所以我们可以将这些步骤其中一些封装起来,比如,jdbc驱动器的注册、jdbc连接对象的获取、还有就是关闭=释放资源都可以封装
封装的时候,比如我们注册驱动器、还有获取连接对象,我们不能将数据源(driver、url、username、password)封装在工具类里面,因为我们操作的数据库或者表格那些的使用是会发生变化的,所以可以写到配置文件里面去
1、数据源写到dp.properties文件中
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_learn?useSSL=false
username=root
password=975806
//注意格式,空格不要,不要写错数据源
2、工具类的封装
JDBCUtils
public class JDBCUtils {
/**
* 1、注册驱动
* 数据库注册只需要一次,静态代码块中
* 2、获取数据库连接
* 封装一个方法,获取Connection 对象
* 3、关闭资源
* 封装一个方法,关闭资源
* 4、将数据库的连接信息放到配置文件(.properties)中去,解决硬编码问题(只能连接特定的数据库)
*/
private static String DRIVER;
private static String URL;
private static String USERNAME;
private static String PASSWORD;
//注册驱动
static {
try {
//读取配置文件中的数据源信息
Properties properties = new Properties();
//通过类加载器架加载classpath下的配置文件
InputStream in = JDBCUtils.class.getClassLoader().getResourceAsStream("dp.properties");
properties.load(in);
//通过Properties对象读取流中的信息
DRIVER = properties.getProperty("driver");
URL = properties.getProperty("url");
USERNAME = properties.getProperty("username");
PASSWORD = properties.getProperty("password");
// Class.forName("com.jdbc.mysql.Driver");
Class.forName(DRIVER);
} catch (Exception e) {
e.printStackTrace();
}
}
/***
* ThreadLocal<T> 类似于集合,保证在同一线程下,拿到的T是同一个
* tl.set();
* tl.get();
* tl.remove();
*
* @return
*/
static ThreadLocal<Connection> tl = new ThreadLocal<>();
//修改Connection的注册,保证同一线程下,不同事务拿到的T是同一个
public static Connection getConnection() {
try{
//1、从tl中获取connection对象
Connection connection = tl.get();
//没有的话,创建一个Connection, 如果tl中有就直接获取
if(connection == null) {
connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
tl.set(connection);
}
return connection;
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
//关闭释放资源
public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
try {
if(resultSet != null) resultSet.close();
if(statement != null) statement.close();
if(connection != null) {
connection.close();
//此时关闭connection,但是ThreadLocal中还有保存这个connection,但是关闭了,下次不能用
tl.remove();//移除关闭的connection
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 事务的封装
*/
//开启事务
public static void start() {
Connection connection = getConnection();
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事务
public static void commit() {
Connection connection = getConnection();
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
} finally {
closeAll(connection, null, null);
}
}
//回滚事务
public static void rollback() {
Connection connection = getConnection();
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
} finally {
closeAll(connection, null, null);
}
}
}
五、三层架构
六、JDBC事务处理
connection.setAutoCommit(false) ;//开启手动事务提交
connection.commit(); //提交事务
connection.rollback(); //事务回滚
转账问题
数据库表格 | 程序架构 |
---|---|
代码
1、DAO层(实现数据库信息的查询,更新等)
Accout : 实体类
public class Account {
private Integer Id;
private String Name;
private Double Money;
private String Password;
@Override
public String toString() {
return "Account{" +
"Id=" + Id +
", Name='" + Name + '\'' +
", Money=" + Money +
", Password='" + Password + '\'' +
'}';
}
public Account(Integer id, String name, Double money, String password) {
Id = id;
Name = name;
Money = money;
Password = password;
}
public Integer getId() {
return Id;
}
public void setId(Integer id) {
Id = id;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public Double getMoney() {
return Money;
}
public void setMoney(Double money) {
Money = money;
}
public String getPassword() {
return Password;
}
public void setPassword(String password) {
Password = password;
}
}
AccountDAO : 实体类操作数据库的接口,定义了查询啊,更新数据库的方法
public interface AccountDAO {
Account selectAccountById(int id);
int updateMoneyById(int id, double money);
}
AccountDAOImpl: 数据库实现类
public class AccountDAOImpl implements AccountDAO{
@Override
public Account selectAccountById(int id) {
Connection connection = null;
PreparedStatement ps = null;
ResultSet res = null;
connection = JDBCUtils.getConnection();
String sql = "select * from account where id = ?";
try {
ps = connection.prepareStatement(sql);
ps.setInt(1, id);
res = ps.executeQuery();
while(res.next()) {
String name = res.getString("name");
String password = res.getString("password");
Double money = res.getDouble("money");
Account account = new Account(id, name, money, password);
return account;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeAll(null, ps, res);
}
return null;
}
@Override
public int updateMoneyById(int id, double money) {
Connection connection = null;
PreparedStatement ps = null;
ResultSet res = null;
connection = JDBCUtils.getConnection();
String sql = "update account set money = money + ? where id = ?";
try {
ps = connection.prepareStatement(sql);
ps.setDouble(1, money);
ps.setInt(2, id);
int i = ps.executeUpdate();
return i;
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeAll(null, ps, res);//设置了统一connetion后,只能在所有事务完成后才关
}
return 0;
}
}
2、Service(业务层,实现银行转账的操作)
AccountService : 定义了实现业务的接口
public interface AccountService {
public String transferAccounts(int fromId, String password, int toId, double money);
}
AccountServiceImpl: 实现业务接口的方法
public class AccountServiceImpl3 implements AccountService{
AccountDAO accountDAO = new AccountDAOImpl();
@Override
public String transferAccounts(int fromId, String password, int toId, double money) {
try {
//1、开启事务
JDBCUtils.start();
Account account = accountDAO.selectAccountById(fromId);
if (account == null) {
return "账户不存在";
}
if (!account.getPassword().equals(password)) return "密码不正确";
//判断余额是否充足
if (account.getMoney() < money) {
return "余额不足";
}
//查询对方卡号
Account account1 = accountDAO.selectAccountById(toId);
if (account == null) return "对方账号不存在";
//我方扣钱
int i = accountDAO.updateMoneyById(fromId, -money);
//对方加钱
// System.out.println(10 / 0);
// System.out.println(10/0);---->这一句是错误代码,回导致上面的扣钱了,发生错误,而下面的没有加钱,因为两次是属于不同事务,
// 我们可以通过修改保证两个connetion是同一个,这样子后手动开始提交事务,就可以保证两个都完成,就不会哦出现一开始的为翁提
int i1 = accountDAO.updateMoneyById(toId, money);
//2、提交事务
JDBCUtils.commit();
return "转账成功";
} catch (Exception e) {
e.printStackTrace();
//3、事务回滚
JDBCUtils.rollback();
}
return "转账失败";
}
}
3、testAccountnService: 测试类
public class testAccoutnService {
public static void main(String[] args) {
AccountService accountService = new AccountServiceImpl3();
String s = accountService.transferAccounts(1, "123", 2, 200);
System.out.println(s);
String s2 = accountService.transferAccounts(1, "123", 2, 200);
System.out.println(s2);
}
}
需要注意的一个点事,我们的转账和另外一个人的入账是2个事务,而且两个事务必须同时成功,或者同时失败,但是有时我们会在两个事务过程中会发生错误,导致转账出去了,但是下一个事务没有成功,也就是说,转出去的事务成功了,二入账的事务没有成功
对于上面的事务错误的问题,我们可以手动开启事务,在所有事务完成后,一并提交 ,这样子就可以解决了。
但是我们会发现,有时事务提交,或者回滚失败?,这是因为Connection不是同一个,两个事务的connection不一样,还是会发生上面叙述的问题:
对于Connection不一致,我们可以有两种办法解决:
1、将Service中使用的Connection传递到DAO层中使用
2、使用ThreadLocal类似实现,保证同一线程下的connection是同一个,不同线程下的是不同的connection,不仅解决了 connnection不一致的问题,还解决了多线程并发的问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VUpgsZ7v-1646457145961)(C:\Users\戴子儒\Desktop\截图\ThreadLocal解决Connection不一致的问题.png)] |
---|
七、数据库连接池Druid
在一个程序中,比如说一时间内有很多用户同时访问连接,会导致出现很大的问题,这样子我们就会引入Druid连接池。Druid是一个高效的数据查询系统,主要解决的是对于大量的基于时序的数据进行聚合查询
druid: 先创建好多个Connection连接对象,如果有空闲的就直接使用,没有的话就等待,使用完成的Connection不会关闭,而是返回到连接池
druid连接池的使用步骤
1、导入jar包(新建更目录下的lib文件夹下)
druid-1.2.0.jar
hamcrest-core-1.3.jar
junit-4.12.jar
mysql-connector-java-8.0.22.jar
2、 编写数据源配置文件(.properties)
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_learn?useSSL=false
username=root
password=975806
#下面四个不写也没有事,因为会有默认值
#初始化连接数 (默认里面已经有10个connection)
initialSize=10
#最大连接数 (当初始化的10个连接池不够用的时候,最大会创建50个connection)
maxActive=50
#最小空闲连接 (当连接池空闲没有被使用的时候, 就会减少到5个)
minIdle=5
#超时等待连接 (当连接数量超过最大连接数的时候,会等待5秒,5秒后就会抛出异常)
maxWait=5000
3、修改JDBCUtils工具类
使用druid给连接池对象赋值
6-17行的代码是修改的内容(对比之前的JDBCUtil是)
public class JDBCDriudUtils {
//定义数据库连接池对象
private static DataSource dataSource;
static {
try {
//读取配置文件中的数据源信息
Properties properties = new Properties();
//通过类加载器架加载classpath下的配置文件
InputStream in = JDBCDriudUtils.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(in);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
}
}
//获取连接池对象
public static DataSource getDataSource() {
return dataSource;
}
static ThreadLocal<Connection> tl = new ThreadLocal<>();
//修改Connection的注册,保证同一线程下,不同事务拿到的T是同一个
//获取connection连接的方法
public static Connection getConnection() {
try {
Connection connection = tl.get();
if(connection == null) {
connection = dataSource.getConnection();
}
return connection;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//关闭释放资源
public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
try {
if(resultSet != null) resultSet.close();
if(statement != null) statement.close();
if(connection != null) {
connection.close(); //此时归还connection到连接池,但是ThreadLocal中还有保存这个connection,但是关闭了,下次不能用
tl.remove();//移除关闭的connection
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 事务的封装
*/
//开启事务
public static void start() {
Connection connection = getConnection();
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事务
public static void commit() {
Connection connection = getConnection();
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
} finally {
closeAll(connection, null, null);
}
}
//回滚事务
public static void rollback() {
Connection connection = getConnection();
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
} finally {
closeAll(connection, null, null);
}
}
}
20220-03-0