目录
dbUtil-toc" style="margin-left:80px;">1、什么是dbUtil
2、lombok插件
dbUtil%E9%80%9A%E8%BF%87xml%E6%9D%A5%E5%AE%9E%E7%8E%B0-toc" style="margin-left:80px;">3、dbUtil通过xml来实现
4、SpringJunit的作用和效果
(1)标记测试方法和配置方法的注解
dbUtil%E9%80%9A%E8%BF%87%E6%B3%A8%E9%87%8A%E6%9D%A5%E5%AE%9E%E7%8E%B0-toc" style="margin-left:80px;">5、dbUtil通过注释来实现
dbUtil%E9%80%9A%E8%BF%87config%E9%85%8D%E7%BD%AE%E7%B1%BB%E6%9D%A5%E5%AE%9E%E7%8E%B0-toc" style="margin-left:80px;">6、dbUtil通过config配置类来实现
dbUtil%E9%80%9A%E8%BF%87aop%E7%9A%84XML%E7%9A%84%E5%AE%9E%E7%8E%B0-toc" style="margin-left:80px;">7、dbUtil通过aop的XML的实现
(1)连接工具类
(2)事务工具类
(3)service层业务事务逻辑实现
dbUtil">1、什么是dbUtil
dbUtil是阿帕奇提供操作数据库的插件,它主要用于减少样板代码,并简化Java程序与数据库的交互。
- 简化ResultSet处理:DBUtils 提供了 ResultSetHandler 接口和一些常用的实现类,用于将 ResultSet 转换为Java对象或集合。
- 简化SQL执行:DBUtils 提供了 QueryRunner 类,用于执行SQL查询和更新操作。它提供了简洁的方法来处理参数和处理结果集。
- 处理资源的关闭:DBUtils 提供了 DbUtils 类,其中包含关闭数据库连接、语句和结果集的方法,以确保资源在使用后被正确释放。
2、lombok插件
如果你有一个人员信息的bean类,如果其中删除某个数属性或者添加某个属性,难道我们要去重新写所有的set、get、构造方法吗,这时候就可以用idea提供的lombok插件来完成这个代码维护。
lombok是用于简化 Java 编码过程中的样板代码(boilerplate code)。它通过注解处理器在编译时生成代码,帮助开发者减少编写样板代码的负担。Lombok 提供了多种注解,用于自动生成常见的 Java 代码,例如 getter、setter、构造函数、toString 方法等。
首先在pom.xml配置lombok配置:
<!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.26</version></dependency>
接下来就可以在pojo类中去简化一个Bean对象的getset等诸多方法。
java">// 这个注解会生成一个无参构造方法。
@NoArgsConstructor
// 这个注解会生成一个包含所有字段的构造方法。
@AllArgsConstructor
// 这个注解是一个综合性注解,它会生成以下常用的方法:
// getter和setter方法
// toString方法
// equals和hashCode方法
// 还会生成一个canEqual方法
@Data
public class Account {private int aid;private String aname;private int amoney;public Account(String aname, int amoney) {this.aname = aname;this.amoney = amoney;}}
- @NoArgsConstructor:是生成的无参构造方法。
- @AllArgsConstructor:是自动生成包含所有字段的构造函数。
- @RequiredArgsConstructor:自动生成包含 final 字段和带有 @NonNull 注解字段的构造函数。
- @Data:自动生成多个常见的样板代码,包括 getter、setter、toString、equals 和 hashCode 方法等。
dbUtil%E9%80%9A%E8%BF%87xml%E6%9D%A5%E5%AE%9E%E7%8E%B0">3、dbUtil通过xml来实现
首先是XML中定义一个数据库连接池Bean,注入数据源:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="driverClass" value="${msg1}"></property><property name="jdbcUrl" value="${msg2}"></property><property name="user" value="${msg3}"></property><property name="password" value="${msg4}"></property></bean>
id属性指的是Bean名称,class这里指的是ComboPooledDataSource,是C3P0提供的一个实现类,这个实现类中有driverClass等jdbc的set方法,通过使用Spring的占位符(${}),这些属性值从外部配置文件中读取。
dataSorce是管理数据库连接的,使用 getConnection() 方法可以从数据源中获取一个 Connection 对象,从而执行 SQL 查询和更新操作。
然后是注入QueryRunner:
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"><constructor-arg name="ds" ref="dataSource"></constructor-arg></bean>
这里的class是org.apache.commons.dbutils.QueryRunner类创建的一个queryRunner的实例,再通过ds这个构造函数注入数据源dataSource。dataSource 是之前定义的 ComboPooledDataSource的Bean,用于管理数据库连接。
然后是注入dao、注入service、注入controller:
<!--注入dao--><bean id="mapperImp" class="com.apesource.dao.AccountMapperImp"><property name="queryRunner" ref="queryRunner"></property></bean><!--注入service--><bean id="service" class="com.apesource.service.AccountServiceImp"><property name="mapper" ref="mapperImp"></property></bean><!--注入controller--><bean id="controller" class="com.apesource.controller.AccountControllerImp"><property name="service" ref="service"></property></bean>
其中dao注入的是queryRunner,用它来执行SQL查询和更新操作,与数据库之间的交互。
其次是dao层中的接口和实现类:
接口IAccountMapper:
java">public interface IAccountMapper {public void save(Account account);public Account findByName(String name);public List<Account> findAll();public void updateById(Account account);public void deleteById(int id);
}
里面定义了增删改查的抽象方法。
其次是AccountMapperImp实现类:
java">public class AccountMapperImp implements IAccountMapper {//操作数据库的核心类QueryRunner queryRunner;public void setQueryRunner(QueryRunner queryRunner) {this.queryRunner = queryRunner;}@Overridepublic void save(Account account) {try {queryRunner.update("insert into account(aname,amoney) value(?,?)",account.getAname(),account.getAmoney());} catch (SQLException throwables) {throwables.printStackTrace();}}@Overridepublic Account findByName(String name) {try {queryRunner.query("select * from account where name = ?",new BeanHandler<Account>(Account.class),name);} catch (Exception e) {e.printStackTrace();}return null;}@Overridepublic List<Account> findAll() {try {return queryRunner.query("select * from account",new BeanListHandler<Account>(Account.class));} catch (SQLException throwables) {throwables.printStackTrace();}return null;}@Overridepublic void updateById(Account account) {try {queryRunner.update("update account set aname=?,amoney=? where aid=?",account.getAname(),account.getAmoney(),account.getAid());} catch (SQLException throwables) {throwables.printStackTrace();}}@Overridepublic void deleteById(int id) {try {queryRunner.update("delete from account where aid=?",id);} catch (SQLException throwables) {throwables.printStackTrace();}}
}
首先实现类继承了接口,重新了其中的方法,其中在中间创建一个成员变量queryRunner用于执行数据库操作的核心类,并通过set方法通过xml注入的方式设置 QueryRunner 实例。
QueryRunner中有两个方法,一个为query,用来查询。一个为update,用来增删改。?为要传的参数,后面依次跟上传入的参数值。
其中findAll()方法中是要查询所有的学生,返回的学生集合,所以这里出现了new BeanListHandler<Account>,因为 BeanListHandler 是 Apache Commons DBUtils 库中用于处理 ResultSet 的一种处理器。它将结果集中的每一行映射为一个 Java 对象,并将这些对象收集到一个 List 中。
service层、controller层:用户传入参数给controller层,controller层传给service层,一层套一层来传递,这里我就只给一个service层的代码,controller层一样的道理。
java">public class AccountServiceImp implements IAccountService {private IAccountMapper mapper;public void setMapper(IAccountMapper mapper) {this.mapper = mapper;}@Overridepublic void save(Account account) {mapper.save(account);}@Overridepublic Account findByName(String name) {return mapper.findByName(name);}@Overridepublic List<Account> findAll() {return mapper.findAll();}@Overridepublic void updateById(Account account) {mapper.updateById(account);}@Overridepublic void deleteById(int id) {mapper.deleteById(id);}
}
通过controller层传递来的参数,通过XML里面的Bean来创建一个mapper对象,再把参数传递给mapper的方法,去dao层中的方法实现逻辑。
4、SpringJunit的作用和效果
然后就是如果我们的想要去调用方法难道要写很多个测试类吗,所以Spring中提供了Junit,用于编写和运行可重复的测试。它主要用于单元测试,但也可以用于集成测试。JUnit 提供了一组注解、断言和运行测试的工具,使得测试代码变得简洁而易于理解。
(1)标记测试方法和配置方法的注解
@Test | 可以运行的测试方法 |
@Before | 在每个@Test运行之前执行。常用于初始化测试环境。 |
@After | 在每个@Test运行之后执行。常用于清理测试环境。 |
@BeforeClass | 在所有@Test运行之前执行一次。常用于耗时的全局初始化。 |
@AfterClass | 在所有@Test运行之后执行一次。常用于全局资源的释放。 |
实例代码(手动管理):
java">public class Test01 {ClassPathXmlApplicationContext applicationContext = null;IAccountController controller = null;@Beforepublic void beforeMethod() {applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");controller = (IAccountController) applicationContext.getBean("controller");}@Afterpublic void afterMethod() {applicationContext.close();}@Testpublic void show1() {controller.save(new Account("大头", 2000));controller.save(new Account("小头", 2000));}@Testpublic void show2() {List<Account> all = controller.findAll();for (int i = 0; i < all.size(); i++) {Account account = all.get(i);System.out.println(account);}}@Testpublic void show3(){controller.updateById(new Account(2,"大头",1000));}
}
上面的代码是去手动的调用applicationContext对象和getBean方法,有没有一种写法可以不用手动的去写这些需求操作呢。这里就用到自动管理:
java">@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Test02 {@AutowiredIAccountController controller;@Testpublic void show1(){controller.save(new Account("林航宇",2000));controller.save(new Account("杨文琪",2000));}@Testpublic void show2(){List<Account> all = controller.findAll();for (int i = 0; i < all.size(); i++) {Account account = all.get(i);System.out.println(account);}}
}
@RunWith()这个注解告诉 JUnit 使用SpringJUnit4ClassRunner类来运行测试。这是一个 Spring 提供的 Runner,它可以在 JUnit 测试中加载 Spring 应用上下文,并将 Spring 的依赖注入机制与 JUnit 测试结合起来。
@ContextConfiguration()这个注解指定了 Spring 应用上下文的配置文件位置。从类路径加载 applicationContext.xml 配置文件。Spring 会根据这个配置文件创建应用上下文,并进行 Bean 的初始化和依赖注入。
dbUtil%E9%80%9A%E8%BF%87%E6%B3%A8%E9%87%8A%E6%9D%A5%E5%AE%9E%E7%8E%B0">5、dbUtil通过注释来实现
其实和通过xml差不多,只不过将bean标签注入换成了扫描注入通过在controller、service、dao层加上注释来完成bean对象的注入。
将原本的构造注入换成了注释来实现标签注入:
java">@Controller("controller")
public class AccountControllerImp implements IAccountController{@Autowiredprivate IAccountService service;
再在XML文件中把bean标签注入删除,换成扫描:
java"><context:component-scan base-package="com.apesource"></context:component-scan>
这里就不过多描述了,跟XML注入非常接近。
dbUtil%E9%80%9A%E8%BF%87config%E9%85%8D%E7%BD%AE%E7%B1%BB%E6%9D%A5%E5%AE%9E%E7%8E%B0">6、dbUtil通过config配置类来实现
其实也跟前两种非常的接近,就是多了一层config配置类,通过加载配置类来连接数据库和创建bean实例。
java">@Configuration
@PropertySource(value = "classpath:jdbc.properties")
public class DataConfig {@Value("${msg1}")private String driverClass;@Value("${msg2}")private String jdbcUrl;@Value("${msg3}")private String user;@Value("${msg4}")private String password;@Beanpublic DataSource dataSource(){try {ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();comboPooledDataSource.setDriverClass(driverClass);comboPooledDataSource.setJdbcUrl(jdbcUrl);comboPooledDataSource.setUser(user);comboPooledDataSource.setPassword(password);return comboPooledDataSource;} catch (PropertyVetoException e) {e.printStackTrace();}return null;}@Beanpublic QueryRunner queryRunner(){return new QueryRunner(dataSource());}}
@Configuration:标记 DataConfig 类为 Spring 的配置类,表示这个类包含一个或多个 @Bean 方法,用于定义 Spring 容器中的 Bean。
@PropertySource("classpath:jdbc.properties"):指定一个或多个属性文件,用于加载应用程序的属性配置。在这里,jdbc.properties 文件位于类路径下。Spring 会将这些属性文件中的属性加载到 Environment 中,以便通过 @Value 注解进行注入。
@Value是将配置文件中的属性值注入到字段中,将从 jdbc.properties 文件中读取并注入到 DataConfig 类中。
然后又定义了DataSource,通过 ComboPooledDataSource 配置数据库连接池,创建了dataSource对象。QueryRunner:使用注入的 DataSource 实例来创建 QueryRunner Bean,用于简化数据库操作。
再通过DataConfig配置类的实现来合作完成ApplicationConfig:
java">@Configuration
@ComponentScan(basePackages = "com.apesource")
@Import(DataConfig.class)
public class ApplicationConfig {
}
这里@ComponentScan是扫描组件扫描的基础包路径,Spring 会扫描 com.apesource 包及其子包中的所有类,自动识别并注册带有 @Component、@Service、@Repository、@Controller 等注解的 Bean。
java">applicationContext = new AnnotationConfigApplicationContext(ApplicationConfig.class);
在测试类中通过AnnotationConfigApplicationContext来获取context。
dbUtil%E9%80%9A%E8%BF%87aop%E7%9A%84XML%E7%9A%84%E5%AE%9E%E7%8E%B0">7、dbUtil通过aop的XML的实现
如果我们在service层写一个逻辑,一个人对另一个人借了多少钱,呢双方的金额都要进行改变,但是如果中间发生了异常,但是没有事务的注入没有发生回滚,那么就会发生逻辑错误,一个人钱少了,一个人没拿到钱。
这时候我们就要进行注入连接工具类和事务工具类了。
<!--连接工具类--><bean id="connectionUtil" class="com.apesource.util.ConnectionUtil"><property name="dataSource" ref="dataSource"></property></bean><!--事务工具类--><bean id="transactionUtil" class="com.apesource.util.TransactionUtil"><property name="connectionUtil" ref="connectionUtil"></property></bean>
在XML中加入这两个注入,一个为管理数据库连接的工具类。在其中注入用构造注入完成了对dataSource的注入。另一个为处理处理数据库事务的工具类,在其中设置 connectionUtil 属性。
如果想要同时提交或回滚那么就要用同一个connection,同一个业务方法的多个dao方法公用一个connection对象,为了确保每个线程都有一个数据库连接,我们就要创建一个Connect再创建一个ConnectionUtil类来管理数据库的连接。如果有连接则就用当前连接,如果没有连接则创建连接。
(1)连接工具类
java">public class ConnectionUtil {//装配数据源DataSource dataSource;public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}//线程区域对象ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();//获取连接public Connection createCon(){try {//1.获取线程内的连接对象Connection connection = threadLocal.get();//2.判断if (connection==null){connection = dataSource.getConnection();//创建连接threadLocal.set(connection);//保存}return connection;} catch (SQLException throwables) {throwables.printStackTrace();}return null;}//移除连接public void removeCon(){threadLocal.remove();//移除连接对象}}
ThreadLocal<Connection>:用于在每个线程中存储一个 Connection 实例。
createCon方法:用于获取当前线程的数据库连接。其中的threadLocal.get():尝试从 ThreadLocal 中获取当前线程的 Connection 实例。如果 ThreadLocal 中没有连接(即 connection 为 null),则通过 dataSource.getConnection() 创建一个新的连接,并将其保存到 ThreadLocal 中。如果有则直接返回当前的connection对象。
removeCon 方法:用于从 ThreadLocal 中移除当前线程的 Connection 实例和连接。
(2)事务工具类
java">public class TransactionUtil {//注入连接工具类ConnectionUtil connectionUtil;public void setConnectionUtil(ConnectionUtil connectionUtil) {this.connectionUtil = connectionUtil;}//开启事务public void beginTx(){try {connectionUtil.createCon().setAutoCommit(false);} catch (SQLException throwables) {throwables.printStackTrace();}}//提交事务public void commitTx(){try {connectionUtil.createCon().commit();} catch (SQLException throwables) {throwables.printStackTrace();}}//回滚事务public void rollbackTx(){try {connectionUtil.createCon().rollback();} catch (SQLException throwables) {throwables.printStackTrace();}}//关闭事务public void closeTx(){try {connectionUtil.createCon().close();//关闭事务connectionUtil.removeCon();//移除事务} catch (SQLException throwables) {throwables.printStackTrace();}}
}
这个类用于管理数据库事务。它封装了事务的开启、提交、回滚和关闭操作,并依赖于 ConnectionUtil 来获取数据库连接,因为里面定义了dataSourse。
然后就是事务的开启、提交、回滚、关闭等方法操作。
开启事务:通过调用createCon()方法获取当前线程的连接,并将其自动提交模式设置为 false。这意味着事务需要手动提交或回滚。
提交/回滚事务:获取当前线程的连接提交/回滚事务。
关闭事务:connectionUtil.createCon().close():关闭当前线程的连接,将其返回到连接池中。connectionUtil.createCon().close():关闭当前线程的连接,将其返回到连接池中。
(3)service层业务事务逻辑实现
呢么在哪个层加入这个转账的方法呢,就是在service的业务逻辑层来完成逻辑的实现。
java">//装配TransactionUtil transactionUtil;public void setTransactionUtil(TransactionUtil transactionUtil) {this.transactionUtil = transactionUtil;}
@Overridepublic void transfer(String sourceName, String targetName, int money) {try {transactionUtil.beginTx();//1.查询数据Account sourceAccount = mapper.findByName(sourceName);Account targetAccount = mapper.findByName(targetName);//2.转账sourceAccount.setAmoney(sourceAccount.getAmoney()-money);targetAccount.setAmoney(targetAccount.getAmoney()+money);//3.修改数据库mapper.updateById(sourceAccount);// int a = 10/0;//模拟异常mapper.updateById(targetAccount);transactionUtil.commitTx();} catch (Exception e) {e.printStackTrace();transactionUtil.rollbackTx();}finally {transactionUtil.closeTx();}}
进行装配,在XML中id为service的bean自动构造注入实例化TransactionUtil事务工具类对象。
首先调用 TransactionUtil 的 beginTx 方法开始一个事务。这会将连接的自动提交模式设置为 false,意味着所有对数据库的操作都将在一个事务中执行,直到提交或回滚。
其次就是对转账这个逻辑进行相应的操作,查看数据、转账、修改数据库。
紧接着调用.commitTx方法提交事务。service逻辑层就完成了事务的逻辑操作了。
在dao层中的实体类完成了注入连接工具类:
java"> ConnectionUtil connectionUtil;public void setConnectionUtil(ConnectionUtil connectionUtil) {this.connectionUtil = connectionUtil;}
呢为什么要在dao层获取连接工具类呢,第一,在TransactionUtil中定义了一个ConnectionUtil的成员变量,所以在dao层实例化一个ConnectionUtil对象就可以被service层中的TransactionUtil对象调用。第二,TransactionUtil 需要通过 ConnectionUtil 来获取数据库连接。第三,dao层是与dataSource有直接关系的,所以用dao层来实例化ConnectionUtil。第四,ConnectionUtil要用dataSource,TransactionUtil要用ConnectionUtil,所以逐一迭代的关系分别放在service层和dao层。
而controller层只用调用transfer方法即可,将参数传入service层。
总而言之,这种实现方法的本质为通过连接对象对事务的统一管理。