工具类的封装和使用
JDBC工具类
1、需要封装的原因
- 若需要频繁操作数据库就需要频繁使用六大步,可将里面重复的代码封装成工具类,减少代码重复
- 可封装的步骤:
- 1、注册数据库驱动(静态代码块)
- 2、获取数据库连接 (方法:getConnection)
- 3、释放资源 (方法:closeAll(Connection , Statement , ResultSet ))
- 4、将配置信息放到properties配置文件中,减少硬编码
2、代码实现
public class JDBCUtil {
/**
* 1、加载数据库驱动
* 数据库驱动只需要加载一次,所以放在静态代码块中
* 2、获取数据库连接
* 封装一个方法,返回一个数据库连接
* public static Connection getConnection(){}
* 3、释放资源
* 封装一个方法,关闭资源
* public static void closeAll(Connection conn,Statement stat,ResultSet rs){}
*/
private static String driver;
private static String url;
private static String username;
private static String password;
static{
try {
//使用Properties类读取db.properties配置文件
Properties pro = new Properties();
//读取配置文件变成一个流
InputStream in = JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");
//使用Properties对象读取流中的内容
pro.load(in);
//从Properties中获取到对应的值赋值上面的四个变量
driver = pro.getProperty("driver");
url = pro.getProperty("url");
username = pro.getProperty("username");
password = pro.getProperty("password");
//1、注册驱动
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//获取数据库连接对象
public static Connection getConnection(){
try {
return DriverManager.getConnection(url,username,password);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//释放资源
public static void closeAll(Connection conn, Statement stat, ResultSet rs){
try {
if(rs != null){
rs.close();
}
if(stat!=null){
stat.close();
}
if(conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
配置文件 db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///java2006
username=root
password=123456
单元测试
单元测试就是对项目中某一个功能(类、方法)进行测试
-
1、导包(与数据库驱动包导入方式一致)
-
2、单元测试中的注解
-
@Test注解(重要) 表示测试方法
-
@Before 在测试方法之前执行
-
@After 在测试方法之后执行
-
@BeforeClass 在测试方法之前执行(优先于@Before执行) 测试方法必须static修饰的方法
-
@AfterClass 在测试方法之后执行(在@After之后执行) 测试方法必须static修饰的方法
-
3、在使用单元测试时候需要注意的问题
- a、单元测试的方法不能有参数
- b、单元测试的方法不能有返回值
- c、单元测试的方法之间不能互相调用
-
4、单元测试的启动方式
- a、点击左边的三角
- b、选择方法右击运行
ORM映射
ORM (Object Relational Mapping):将数据库中的表的数据,一行一行的映射到Java对象中
1、实体类的设计规范
JavaBean设计规范
1、类名数据库中的表名一致
2、类中的属性名与数据库中的字段名一致
3、使用类的封装私有属性,对外提供set、get方法
4、提供有参和无参构造方法
5、所有基本类型使用他们的包装类
6、有必要的时候,这个类要实现序列化接口
2、代码实现
public class ORMDemo{
public static void main(String[] args) throws SQLException {
//2、获取数据库连接对象
Connection conn = JDBCUtil.getConnection();
//3、获取数据库操作对象
PreparedStatement ps = conn.prepareStatement("select * from emp");
//4、执行sql语句,返回结果
ResultSet rs = ps.executeQuery();
//5、处理结果 (ORM)
//定义一个list集合保存多个Emp对象
List<Emp> empList = new ArrayList<>();
while(rs.next()){
int empno = rs.getInt("empno");
String ename = rs.getString("ename");
String job = rs.getString("job");
int mgr = rs.getInt("mgr");
Date hiredate = rs.getDate("hiredate");
double sal = rs.getDouble("sal");
double comm = rs.getDouble("comm");
int deptno = rs.getInt("deptno");
//将数据库中获取的数据,存放到对象中
Emp emp = new Emp(empno,ename,job,mgr,hiredate,sal,comm,deptno);
//将对象存放到集合中
empList.add(emp);
}
for (Emp emp : empList) {
System.out.println(emp);
}
//6、释放资源
JDBCUtil.closeAll(conn,ps,rs);
}
}
DAO 数据访问对象(Data Access Object)
- DAO 实现了业务逻辑与数据库访问相分离。对同一张表的所有操作封装在XxxDaoImpl对象中。根据增删改查的不同功能实现具体的方法(insert、update、delete、select、selectAll)。
Service 业务
1、什么是业务
- 代表用户完成的一个业务功能,可以由一个或多个DAO的调用组成。(软件所提供的一个功能都叫业务)
2、转账业务开发
public class AccountServiceImpl implements AccountService {
AccountDao accountDao = new AccountDaoImpl();
@Override
public String zhuanZhang(String cardno, String password, String tocardno, double money) {
//1、验证账户是否存在
Account account = accountDao.selectAccount(cardno);
if(account == null){
return "账户不存在";
}
//2、验证密码是否正确
if(!password.equals(account.getPassword())){
return "密码错误";
}
//3、验证余额是否足够
if(money > account.getMoney()){
return "余额不足";
}
//4、验证对方卡号是否存在
Account account1 = accountDao.selectAccount(tocardno);
if(account1 == null){
return "对方账户不存在";
}
//5、己方账号扣钱
accountDao.updateAccount(money,cardno);
//6、对方账号加钱
accountDao.updateAccount(-money,tocardno);
return "转账成功";
}
}
事务
1、什么是事务
- 事务:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);
2、事务的四大特征
事务的四大特性:
- 1 、原子性
事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做 - 2 、一致性
事 务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。 - 3 、隔离性
一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。 - 4 、持续性
也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。
3、事务的使用
在JDBC 中,获得 Connection 对象即开始事务–提交或回滚–关闭连接。其事务操作是
- conn.setAutoCommit(false);//设置事务为手动提交,不设置时为手动提交
- conn.commit();//手动提交事务
- conn.rollback();//手动回滚事务
4、由转账案例引发的事务
- 没有事务支持的转账案例不具备原子性,若转账出现异常会导致数据不完整
- 若直接在Service层开启事务提交事务回滚事务的话会导致Dao层和Service层的Connection不一致,无法实现事务回滚
4.1解决方案1:传递 Connection
- 如果使用传递Connection,容易造成接口污染(BadSmell)。
- 定义接口是为了更容易更换实现,而将 Connection定义在接口中,会造成污染当前接口。
4.2解决方案2:ThreadLocal
- 可以将整个线程中(单线程)中,存储一个共享值。
- 线程拥有一个类似 Map 的属性,键值对结构<ThreadLocal对象,值>。
4.3使用ThreadLocal更新JDBC工具类
public class JDBCUtil {
private static String driver;
private static String url;
private static String username;
private static String password;
static{
try {
//读取外部配置文件
Properties pro = new Properties();
InputStream in = JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");
pro.load(in);
driver = pro.getProperty("driver");
url = pro.getProperty("url");
username = pro.getProperty("username");
password = pro.getProperty("password");
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* ThreadLocal类的特点就是同一个线程拿到的Connection就是同一个
*/
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
public static Connection getConn(){
//1、先从ThreadLocal中获取Connection对象
Connection connection = tl.get();
try {
//2、ThreadLocal中没有Connection
if(connection == null){
//3、通过JDBC获取一个Connection连接,然后存到ThreadLocal中
Connection conn = DriverManager.getConnection(url, username, password);
tl.set(conn);
return conn;
}
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
public static void closeAll(Connection conn, Statement stat, ResultSet rs){
try {
if(rs!=null){
rs.close();
}
if(stat != null){
stat.close();
}
if(conn!=null){
conn.close();
tl.remove(); //当业务层的使用完之后,关闭了Connection,就从ThreadLocal中移除掉
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4.4 事务封装
- 将事务的开启、提交、回滚都封装在工具类中,业务层调用即可。
//开启事务
public static void start(){
Connection conn = getConn();
try {
conn.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事务
public static void commit(){
Connection conn = getConn();
try {
conn.commit();
} catch (SQLException e) {
e.printStackTrace();
}finally {
closeAll(conn,null,null);
}
}
//回滚事务
public static void rollback(){
Connection conn = getConn();
try {
conn.rollback();
} catch (SQLException e) {
e.printStackTrace();
}finally {
closeAll(conn,null,null);
}
}
4.5最终的转账业务
public class AccountServiceImpl1 implements AccountService {
AccountDao accountDao = new AccountDaoImpl();
@Override
public String zhuanZhang(String cardno, String password, String tocardno, double money) {
try {
//一、设置事务为手动提交 (开启事务)
JDBCUtil.start();
//1、验证账户是否存在
Account account = accountDao.selectAccount(cardno);
if(account == null){
return "账户不存在";
}
//2、验证密码是否正确
if(!password.equals(account.getPassword())){
return "密码错误";
}
//3、验证余额是否足够
if(money > account.getMoney()){
return "余额不足";
}
//4、验证对方卡号是否存在
Account account1 = accountDao.selectAccount(tocardno);
if(account1 == null){
return "对方账户不存在";
}
//5、己方账号扣钱
accountDao.updateAccount(money,cardno);
//System.out.println(10 / 0);
accountDao.updateAccount(-money,tocardno);
//6、对方账号加钱
//二、提交事务
JDBCUtil.commit();
return "转账成功";
} catch (Exception e) {
e.printStackTrace();
//三、回滚事务
JDBCUtil.rollback();
}
return "转账失败";
}
}