Spring框架-第二篇
第一章:Spring的IOC实现CRUD
1.1-需求和技术要求
- 需求:实现账户的 CRUD 操作
- 技术:
- 使用 spring 的 IoC 实现对象的管理
- 使用 dbutils作为持久层解决方案
- 使用 c3p0 数据源
1.2-环境搭建
1.2.1-Maven工程导入依赖
<dependencies>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--dbUtils-->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<!--c3p0-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
</dependencies>
1.2.2-数据库脚本
CREATE DATABASE IF NOT EXISTS db1
USE db1
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(40),
money FLOAT
)CHARACTER SET utf8 COLLATE utf8_general_ci;
INSERT INTO account(NAME,money) VALUES('aaa',1000);
INSERT INTO account(NAME,money) VALUES('bbb',1000);
INSERT INTO account(NAME,money) VALUES('ccc',1000);
1.2.3-创建实体类
public class Account {
private int id;
private String name;
private float money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getMoney() {
return money;
}
public void setMoney(float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
1.2.4-创建持久层
接口
public interface IAccountDao {
/**
* 查询所有账户信息
*
* @return
*/
List<Account> findAll();
/**
* 根据id查询一个数据
* @param id
* @return
*/
Account findOne(int id);
/**
* 添加一个新数据
* @param account
*/
void save(Account account);
/**
* 更新
* @param account
*/
void update(Account account);
/**
* 删除
* @param id
*/
void del(int id);
}
实现类
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner = null;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
@Override
public List<Account> findAll() {
try{
String sql = "select * from account";
return runner.query(sql,new BeanListHandler<Account>(Account.class));
}catch (Exception e){
e.printStackTrace();
}
return null;
}
@Override
public Account findOne(int id) {
try{
String sql = "select * from account where id=?";
return runner.query(sql,new BeanHandler<>(Account.class),id);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
@Override
public void save(Account account) {
try{
String sql = "INSERT INTO account(NAME,money) VALUES(?,?)";
runner.update(sql,account.getName(),account.getMoney());
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void update(Account account) {
try{
String sql = "UPDATE account SET NAME=?,money=? WHERE id=?";
runner.update(sql,account.getName(),account.getMoney(),account.getId());
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void del(int id) {
try{
String sql = "DELETE FROM account WHERE id=?";
runner.update(sql,id);
}catch (Exception e){
e.printStackTrace();
}
}
}
1.2.5-创建业务层
接口
public interface IAccountServices {
/**
* 查询所有账户信息
*
* @return
*/
List<Account> findAll();
/**
* 根据id查询一个数据
* @param id
* @return
*/
Account findOne(int id);
/**
* 添加一个新数据
* @param account
*/
void save(Account account);
/**
* 更新
* @param account
*/
void update(Account account);
/**
* 删除
* @param id
*/
void del(int id);
}
实现类
public class AccountServicesImpl implements IAccountServices {
private IAccountDao dao = null;
public void setDao(IAccountDao dao) {
this.dao = dao;
}
@Override
public List<Account> findAll() {
return dao.findAll();
}
@Override
public Account findOne(int id) {
return dao.findOne(id);
}
@Override
public void save(Account account) {
dao.save(account);
}
@Override
public void update(Account account) {
dao.update(account);
}
@Override
public void del(int id) {
dao.del(id);
}
}
1.2.6-创建配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
1.3-配置步骤
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置IAccountService对象-->
<bean id="accountService" class="cn.lpl666.services.impl.AccountServicesImpl">
<property name="dao" ref="accountDao"></property>
</bean>
<!--配置IAccountDao对象-->
<bean id="accountDao" class="cn.lpl666.dao.impl.AccountDaoImpl">
<property name="runner" ref="queryRunner"></property>
</bean>
<!--配置QueryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="c3p0"></constructor-arg>
</bean>
<!--配置数据源-->
<bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--注入连接数据库必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db1"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
1.4-测试
public class ClientTest {
// 创建Spring容器
private ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 获取IAccountServices对象
private IAccountServices services = ac.getBean("accountService",IAccountServices.class);
/**
* 查询所有
*/
@Test
public void findAll(){
List<Account> all = services.findAll();
System.out.println(all);
}
/**
* 查询一个
*/
@Test
public void findOne(){
Account account = services.findOne(1);
System.out.println(account);
}
/**
* 添加
*/
@Test
public void save(){
Account account = new Account();
account.setName("test2");
account.setMoney(40000);
services.save(account);
}
/**
* 修改
*/
@Test
public void update(){
Account account = services.findOne(1);
account.setMoney(9999);
services.update(account);
}
/**
* 删除
*/
@Test
public void del(){
services.del(6);
}
}
第二章:基于注解的IOC配置
2.1-环境搭建
Maven引入依赖包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
持久层接口和实现类
接口
public interface IAccountDao {
void save();
}
实现类
@Component("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Override
public void save() {
System.out.println("保存了账户");
}
}
服务层接口和实现类
接口
public interface IAccountServices {
void save();
}
实现类
@Component("accountService")
@Scope("prototype")
public class AccountServicesImpl implements IAccountServices {
@Autowired
@Qualifier("accountDao")
private IAccountDao dao = null;
@Value("10")
private int number;
@Override
public void save() {
dao.save();
System.out.println(number);
}
}
配置文件bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--
告知 spring 创建容器时要扫描的包
cn.lpl666包下所有的dao层、服务层、实体类都会被扫描
-->
<context:component-scan base-package="cn.lpl666"/>
</beans>
测试类
public class ClientUI {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountServices services =(IAccountServices) ac.getBean("accountService");
services.save();
}
}
2.2-常用注解
2.2.1-用于创建对象的
相当于:<bean id="" class="">
@Component
作用:把资源让 spring 来管理。相当于在 xml 中配置一个 bean。
属性:value,指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写
@Component("accountService")
public class AccountServicesImpl implements IAccountServices {}
@Controller、 @Service、 @Repository
他们三个注解都是针对一个的(@Component)衍生注解,他们的作用及属性都是一模一样的。
他们只不过是提供了更加明确的语义化。
@Controller:一般用于表现层的注解。
@Service:一般用于业务层的注解。
@Repository:一般用于持久层的注解。
细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。
2.2.2-用于注入数据的
相当于: <property name="" value="">
或 <property name="" ref="">
@Autowired
作用:自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个 类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到 就报错。
@Qualifier
作用:在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和 @Autowire 一起使用;但是给方法参数注入时,可以独立使用。
属性:value:指定 bean 的 id。
@Component("accountService")
public class AccountServicesImpl implements IAccountServices {
@Autowired
@Qualifier("accountDao")
private IAccountDao dao = null;
)
@Resource
作用: 直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。
属性: name,指定 bean 的 id。
@Component("accountService")
public class AccountServicesImpl implements IAccountServices {
@Resource(name="accountDao")
private IAccountDao dao = null;
)
@Value
作用: 注入基本数据类型和 String 类型数据的
属性: value,用于指定值
@Value("10")
private int number;
2.2.3-用于改变作用范围的
相当于:<bean id="" class="" scope="">
@Scope
作用: 指定 bean 的作用范围。
属性: value,指定范围的值。
- 取值:singleton prototype request session globalsession
@Component("accountService")
@Scope("prototype")
public class AccountServicesImpl implements IAccountServices {}
2.2.4-和生命周期相关的
相当于:<bean id="" class="" init-method="" destroy-method="">
@PostConstruct
作用:用于指定初始化方法。
@PreDestroy
作用:用于指定销毁方法
2.2.5-注解和 XML 的选择问题
注解的优势: 配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。
XML 的优势: 修改时,不用改源码。不涉及重新编译和部署
Spring 管理 Bean 方式的比较:
2.3-注解IOC实现CRUD
2.3.1-需求和技术要求
同上第一章:1.1-需求和技术要求
2.3.2-Maven工程导入依赖
<dependencies>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--dbUtils-->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<!--c3p0-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<!--spring-test-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
</dependencies>
2.3.3-数据库脚本
同上第一章:1.2.2-数据库脚本
2.3.4-创建实体类
同上第一章:1.2.3-创建实体类
2.3.5-创建持久层
接口:
public interface IAccountDao {
/**
* 查询所有账户信息
*
* @return
*/
List<Account> findAll();
/**
* 根据id查询一个数据
* @param id
* @return
*/
Account findOne(int id);
/**
* 添加一个新数据
* @param account
*/
void save(Account account);
/**
* 更新
* @param account
*/
void update(Account account);
/**
* 删除
* @param id
*/
void del(int id);
}
实现类:
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner = null;
@Override
public List<Account> findAll() {
try{
String sql = "select * from account";
return runner.query(sql,new BeanListHandler<Account>(Account.class));
}catch (Exception e){
e.printStackTrace();
}
return null;
}
@Override
public Account findOne(int id) {
try{
String sql = "select * from account where id=?";
return runner.query(sql,new BeanHandler<>(Account.class),id);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
@Override
public void save(Account account) {
try{
String sql = "INSERT INTO account(NAME,money) VALUES(?,?)";
runner.update(sql,account.getName(),account.getMoney());
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void update(Account account) {
try{
String sql = "UPDATE account SET NAME=?,money=? WHERE id=?";
runner.update(sql,account.getName(),account.getMoney(),account.getId());
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void del(int id) {
try{
String sql = "DELETE FROM account WHERE id=?";
runner.update(sql,id);
}catch (Exception e){
e.printStackTrace();
}
}
}
2.3.6-创建业务层
接口:
public interface IAccountServices {
/**
* 查询所有账户信息
*
* @return
*/
List<Account> findAll();
/**
* 根据id查询一个数据
* @param id
* @return
*/
Account findOne(int id);
/**
* 添加一个新数据
* @param account
*/
void save(Account account);
/**
* 更新
* @param account
*/
void update(Account account);
/**
* 删除
* @param id
*/
void del(int id);
}
实现类:
@Service("accountService")
@Scope("prototype")
public class AccountServicesImpl implements IAccountServices {
@Autowired
@Qualifier("accountDao")
private IAccountDao dao = null;
@Override
public List<Account> findAll() {
return dao.findAll();
}
@Override
public Account findOne(int id) {
return dao.findOne(id);
}
@Override
public void save(Account account) {
dao.save(account);
}
@Override
public void update(Account account) {
dao.update(account);
}
@Override
public void del(int id) {
dao.del(id);
}
}
2.3.7-创建jdbc配置文件
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db1
jdbc.user=root
jdbc.password=root
2.3.8-创建jdbc配置类
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.user}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建QueryRunner对象
* @param ds
* @return
*/
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("dataSource") DataSource ds){
return new QueryRunner(ds);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name = "dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setUser(username);
ds.setPassword(password);
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
2.3.9-创建Spring配置类
/**
【Configuration】
标识该类是配置类,相当于bean.xml配置文件
【ComponentScan】
用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的:
<context:component-scan base-package="cn.lpl666"/>是一样的。
*/
@Configuration // 表示该类是配置类
@ComponentScan("cn.lpl666") // spring容器要扫描的包
@Import({JdbcConfig.class}) // 导入其他配置类
public class SpringConfiguration {
}
2.3.10-测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class ClientTest {
// 创建Spring容器
private ApplicationContext as = null;
// 获取IAccountServices对象
@Autowired
private IAccountServices services = null;
/**
* 查询所有
*/
@Test
public void findAll(){
List<Account> all = services.findAll();
System.out.println(all);
}
/**
* 查询一个
*/
@Test
public void findOne(){
Account account = services.findOne(1);
System.out.println(account);
}
/**
* 添加
*/
@Test
public void save(){
Account account = new Account();
account.setName("test3");
account.setMoney(40000);
services.save(account);
}
/**
* 修改
*/
@Test
public void update(){
Account account = services.findOne(1);
account.setMoney(9999);
services.update(account);
}
/**
* 删除
*/
@Test
public void del(){
services.del(6);
}
}
2.4-新注解
@Configuration
作用: 用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用 AnnotationApplicationContext(有@Configuration 注解的类.class)。
属性: value:用于指定配置类的字节码
@Configuration // 表示该类是配置类
public class SpringConfiguration {
}
@ComponentScan
作用: 用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的:<context:component-scan base-package="cn.lpl666"/>
是一样的。
属性: basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。
@Configuration // 表示该类是配置类
@ComponentScan("cn.lpl666") // spring容器要扫描的包
public class SpringConfiguration {
}
@Bean
作用: 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。
属性: name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。
public class JdbcConfig {
/**
* 创建QueryRunner对象
* @param ds
* @return
*/
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("dataSource") DataSource ds){
return new QueryRunner(ds);
}
}
@PropertySource
作用:用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到 properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。
属性: value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {}
@Import
作用: 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问 题。 属性: value[]:用于指定其他配置类的字节码。
@Import({JdbcConfig.class}) // 导入其他配置类
public class SpringConfiguration {
}
2.5-通过注解获取容器
ApplicationContext ac =
new AnnotationConfigApplicationContext(SpringConfiguration.class);
2.6- Spring 整合 Junit
2.6.1-问题
在测试类中,每个测试方法都有以下两行代码:
ApplicationContext ac =new AnnotationConfigApplicationContext(SpringConfiguration.class);
IAccountService as = ac.getBean("accountService",IAccountService.class);
这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。
2.6.2-解决思路分析
针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建 spring 容器,我们就
无须手动创建了,问题也就解决了。
junit 是无法实现的,因为它自己都无法知晓我们是否使用了 spring 框架,更不用说帮我们创建 spring 容器了。不过好在,junit 给我们暴露了一个注解,可以让我们替换掉它的运行器。
这时,我们需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我
们只需要告诉它配置文件在哪就行了。
2.6.3-配置步骤
第一步: Maven引入依赖包,并且junit的版本要在4.12或以上版本
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<!--spring-test-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
第二步:使用@RunWith 注解替换原有运行器
@RunWith(SpringJUnit4ClassRunner.class)
public class ClientTest {}
第三步:使用@ContextConfiguration 指定 spring 配置文件的位置
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
// 若是xml配置,则使用:@ContextConfiguration(locations = "classpath:bean.xml")
public class ClientTest {}
@ContextConfiguration 注解:
- locations 属性:用于指定配置文件的位置。
- 如果是类路径下,需要用 classpath:表明 classes 属性:用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置
第四步:使用@Autowired 给测试类中的变量注入数据
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class ClientTest {
// 获取IAccountServices对象
@Autowired
private IAccountServices services = null;
}
2.6.4-为什么不把测试类配置到xml中
在解释这个问题之前,先解除大家的疑虑,配到 XML 中能不能用呢?
答案是肯定的,没问题,可以使用。
那么为什么不采用配置到 xml 中的方式呢?
这个原因是这样的:
第一:当我们在 xml 中配置了一个 bean,spring 加载配置文件创建容器时,就会创建对象。
第二:测试类只是我们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问
题,所以创建完了,并没有使用。那么存在容器中就会造成资源的浪费。
所以,基于以上两点,我们不应该把测试配置到 xml 文件中。