//1. 事务(重点)
//案例: 转账功能
//发送方减钱
int result = accountDao.update(sendAccount);
//完成一个功能后,后面的接收方操作无法完成,数据出现问题,如何处理
//接收方加钱
result = accountDao.update(recvAccount);
/*
事务:将转账功能的多条SQL语句放入事务中,要么都成功,则提交;要么都失败,则回滚
事务如何加?在哪个层次加,为什么?
service层,如果只完成一个SQL语句,则回滚;都完成了则提交;例如
*/
try{
//开启事务
//发送方减钱
int result = accountDao.update(sendAccount);
//异常
//接收方加钱
result = accountDao.update(recvAccount);
//提交事务
}catch(Exception e){
//回滚
}
/*
问题1:出现异常后,无法进行回滚
原因:处理事务的连接对象和SQL操作的连接对象不是同一个
解决方案:
1.将事务处理的连接对象传入到dao层,共SQL操作使用--不推荐,容易造成接口污染
2.通过ThreadLocal来确保service和dao层操作同一对象--推荐
ThreadLocal的用法类似Map集合,通过键值对存储;
可以在service和dao层中操作同一个共享值,这个共享值就是连接对象
问题2:执行完一个SQL,关闭了连接对象;再次执行SQL会报错
解决方案:统一在提交或回滚事务时,才关闭连接对象
*/
==============DBUtils工具类==============
public class DBUtils {
private static Properties p = new Properties();
private static ThreadLocal<Connection> th = new ThreadLocal<>();
//静态代码块:只加载一次
static{
//反射对象调用getResourceAsStream
//从src目录下获取到db.properties的资源
try {
InputStream is = DBUtils.class.getResourceAsStream("/db.properties");
p.load(is);
Class.forName(p.getProperty("driver"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection(){
Connection conn = null;
try {
conn = th.get(); //从ThreadLocal中获取连接对象
if(conn==null){
conn = DriverManager.getConnection(p.getProperty("url"), p.getProperty("username"), p.getProperty("password"));
th.set(conn); //将连接对象存储到ThreadLocal
}
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
public static void begin() throws SQLException {
Connection conn = getConnection();
conn.setAutoCommit(false); //开启事务
}
public static void commit() throws SQLException {
Connection conn = getConnection();
conn.commit();
closeAll(conn); //关闭资源
}
public static void rollback(){
Connection conn = getConnection();
try {
conn.rollback();
closeAll(conn); //关闭资源
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void closeAll(AutoCloseable...cs){
for(AutoCloseable c : cs){
if(c!=null){
try {
c.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
th.remove(); //移除th对象
}
}
//==========转账业务层代码=========
try {
//开启事务
DBUtils.begin(); //开启事务
sendAccount.setMoney(sendAccount.getMoney()-money);
int result = accountDao.update(sendAccount);
System.out.println("发送方修改:"+result);
//int i=1/0; //模拟异常
recvAccount.setMoney(recvAccount.getMoney()+money);
result = accountDao.update(recvAccount);
System.out.println("发送方修改:"+result);
DBUtils.commit(); //提交事务
}catch (Exception e){
System.out.println("事务回滚了");
DBUtils.rollback(); //事务回滚
return "转账失败~~!";
}
return "转账成功~~!";
/*
2. 三层架构
什么是三层架构?
与数据库操作的功能,划分了三个层次,分别是:表示层(main),业务层,数据访问层
表示层:
将准备的参数传给业务层,并接收业务层反馈(main)
业务层:(接口与实现类)
业务逻辑处理,调用dao层方法,并接收返回结果
数据访问层:(接口与实现类)
与数据库的交互操作
*/
//完整的三层架构的操作步骤:
//1.创建实体类
//2.DBUtils的工具类
//3.dao层的接口与实现类
//4.service层的接口与实现类
//5.通过测试类进行测试,具体结果反馈,返回到测试类中
//注意:此处加了接口与实现类,方便后续的扩展与维护
public class UserTest {
public static void main(String[] args) {
//面向接口编程:
UserService userService = new UserServiceImpl();
List<User> list = userService.selectAll(); //查询所有
}
}
/*
3. DaoUtils(重点)
dao层的jdbc操作有太多的冗余代码
DaoUtils就是针对dao层代码进行封装,这样可以进行增删改查的复用
*/
//----------DaoUtils封装---------
//对dao层的增删改查的jdbc进行优化
public class DaoUtils {
//增删改的封装
//返回值--int(与数据库返回相关)
//参数:增删改中编号的数据抽取到参数-sql,传入数据
public static int commonUpdate(String sql,Object... objs) {
Connection conn = null;
PreparedStatement prst = null;
try {
conn = DBUtils.getConnection();
prst = conn.prepareStatement(sql);
//有多少个参数,就有多少个Object
for (int i = 0; i < objs.length; i++) {
prst.setObject(i + 1, objs[i]);
}
return prst.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtils.closeAll(prst, conn);
}
return 0;
}
//DQL的封装:
//返回值:集合的泛型
//参数:sql, 反射对象,所有参数
public static <T> List<T> commonQuery(String sql,Class<T> clazz,Object...objs){
Connection conn = null;
PreparedStatement prst = null;
ResultSet rs = null;
List<T> list = new ArrayList<>();
try {
conn = DBUtils.getConnection();
prst = conn.prepareStatement(sql);
for(int i=0;i<objs.length;i++){
prst.setObject(i+1,objs[i]);
}
rs = prst.executeQuery();
Field[] fields = clazz.getDeclaredFields();
while(rs.next()){
//如何将数据进行ORM
//rs.getObject传字段名(未知)-->已知属性名,只要属性名和字段名匹配,则可以注入值
//获取属性名的数组:
//循环遍历取出属性名,并通过属性名充当字段,得到字段内容
T t = clazz.newInstance();
for(Field f:fields){ //获取field对象
Object value = rs.getObject(f.getName()); //字段名一定要与属性名一致
f.setAccessible(true); //开启权限
f.set(t,value); //赋值
}
list.add(t); //将实体对象存入List
}
} catch (Exception e) {
e.printStackTrace();
}finally {
DBUtils.closeAll(rs,prst,conn);
}
return list;
}
}
//----------PersonDaoImpl---------
//dao层--数据访问层(用于做jdbc操作)
public class PersonDaoImpl {
public int insert(Person p) {
String sql = "insert into person(name,age,bornDate,email,address) values(?,?,?,?,?)";
return DaoUtils.commonUpdate(sql,p.getName(),p.getAge(),p.getBornDate(),p.getEmail(),p.getAddress());
}
public int update(Person p) {
String sql = "update person set name=?,age=? where id=?";
return DaoUtils.commonUpdate(sql,p.getName(),p.getAge(),p.getId());
}
public int delete(int id) {
String sql = "delete from person where id=?";
return DaoUtils.commonUpdate(sql,id);
}
public Person selectById(int id) {
String sql = "select * from person where id=?";
List<Person> list = DaoUtils.commonQuery(sql,Person.class,id);
if(list.size()>0){
return list.get(0);
}
return null;
}
public List<Person> selectAll() {
String sql = "select * from person";
return DaoUtils.commonQuery(sql,Person.class);
}
}
//1.Druid连接池(重点)
//连接池:
//概述:在池中预先放入多个连接对象,当用户使用连接对象,从池子中取出;用完了回收到池子中
//好处:减少创建和销毁连接对象的数目,提高了性能
//原理:(复用机制)
// 在集合中放入10个连接对象
// 如果有用户使用连接对象,则从集合中获取,并删除集合中的对象
// 如果执行完毕,调用close,回收资源(将连接对象,重新添加到集合,给另一个用户复用)
//Druid连接池: 是阿里巴巴提供的,公认的性能最好的一款连接池产品;其它-c3p0,dbcp
//使用步骤:
//1.导入Druid连接池的jar包
//2.编写Druid的配置文件db.properties;(注意:key固定,值可变更)
//3.加载配置文件,获取数据源,从而得到连接对象
//4.close已经变为了回收(连接池里面有重写)
---------------db.properties配置---------------
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb1
username=root
password=123
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 60000毫秒/1000等于60秒 -->
maxWait=5000
------------DBUtils连接池工具类-------------
public class DBUtils {
private static DataSource dataSource;
private static Properties p = new Properties();
//静态代码块:只加载一次
static{
//反射对象调用getResourceAsStream
//从src目录下获取到db.properties的资源
try {
InputStream is = DBUtils.class.getResourceAsStream("/db.properties");
p.load(is); //加载连接池配置
//根据传入的配置,获取数据源,数据源中包含了连接池的操作
dataSource = DruidDataSourceFactory.createDataSource(p);
} catch (Exception e) {
e.printStackTrace();
}
}
public static DataSource getDataSource(){
return dataSource; //获取数据源
}
public static Connection getConnection(){
Connection conn = null;
try { //从连接池中获取连接对象
conn = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
}
//2.DaoUtils的第三方jar
//DaoUtils第三方jar包引入
//内部实现:和我们自己写的DaoUtils是类似的,提供了,我们就不用自己写了
//第三方的jar做了除级别crud之外的其他sql语句的封装;例如:聚合,连接查询
//步骤:
//1.导入dbutils的jar包(DaoUtils的实现)
//2. 通过QueryRunner调用update和query方法实现增删改查
public class PersonDaoImpl {
//使用QueryRunner实现增删改查功能
private QueryRunner queryRunner = new QueryRunner(DBUtils.getDataSource());
public int insert(Person p) {
String sql = "insert into person(name,age,bornDate,email,address) values(?,?,?,?,?)";
try {
return queryRunner.update(sql,p.getName(),p.getAge(),p.getBornDate(),p.getEmail(),p.getAddress());
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
public int update(Person p) {
String sql = "update person set name=?,age=? where id=?";
try {
return queryRunner.update(sql,p.getName(),p.getAge(),p.getId());
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
public int delete(int id) {
String sql = "delete from person where id=?";
try {
return queryRunner.update(sql,id);
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
public Person selectById(int id) {
String sql = "select * from person where id=?";
try {
return queryRunner.query(sql, new BeanHandler<>(Person.class), id);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public List<Person> selectAll() {
String sql = "select * from person";
try {
return queryRunner.query(sql,new BeanListHandler<>(Person.class));
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}