高山仰止-景行行止
4、功能完善
添加更新、删除、查询byID、查询所有的功能
至此,spring的配置文件已经全部写好,但是功能还较为单一,我们来补充一下其他的CURD功能,这部分工作不算难。只需要把studentDao和studentService的相关类完善后进行测试即可:
完善studentDao.class
public interface StudentDao {
//保存
void saveStudent(Student student);
//更新
void updateStudent(Student student);
//删除
void deleteStudent(int id);
//根据id查询
Student selectStudentById(int id);
//查询全部
List<Student> selectAll();
}
完善studentDaoImpl.class
/**
* 更新信息,根据id更新内容
* @param student 学生对象
*/
@Override
public void updateStudent(Student student) {
String sql = "update student set name = ?,age = ? where id = ?";
//三个占位分别表示姓名、年龄、id
try {
queryRunner.update(sql,student.getName(),student.getAge(),student.getId());
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 根据id删除
* @param id
*/
@Override
public void deleteStudent(int id) {
String sql = "delete from student where id = ?";
try {
queryRunner.update(sql,id);
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 根据id查询
* @param id
* @return
*/
@Override
public Student selectStudentById(int id) {
String sql = "select * from student where id = ?";
try {
Student student = queryRunner.query(sql, new BeanHandler<Student>(Student.class), id);
return student;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/**
* 查询所有学生
* @return
*/
@Override
public List<Student> selectAll() {
String sql = "select * from student";
try {
List<Student> studentList = queryRunner.query(sql, new BeanListHandler<Student>(Student.class));
return studentList;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
完善studentService.class
public interface StudentDao {
//保存
void saveStudent(Student student);
//更新
void updateStudent(Student student);
//删除
void deleteStudent(int id);
//根据id查询
Student selectStudentById(int id);
//查询全部
List<Student> selectAll();
}
完善studentServiceImpl.class
@Override
public void updateStudent(Student student) {
studentDao.updateStudent(student);
}
@Override
public void deleteStudent(int id) {
studentDao.deleteStudent(id);
}
@Override
public Student selectStudentById(int id) {
return studentDao.selectStudentById(id);
}
@Override
public List<Student> selectAll() {
return studentDao.selectAll();
}
测试功能
为了方便测试,我们在测试类中提取studentServiceImpl对象为成员变量,把容器的获取和studentServiceImpl对象的获取放到@before里:
注意:这里我修改了增加方法的代码,因为修改了代码要再次测试一下原有功能。
public class TestIoCandDI {
private StudentService studentService;
@Before
public void setUp(){
//拿到容器
ApplicationContext ac =
new ClassPathXmlApplicationContext("application-context.xml");
//从容器中拿到service,第一个参数是容器中bean的id,第二个参数指定bean的类型.
//如果不指定也可以,拿到的是Object类,需要通过强转.
//StudentService studentService = (StudentService) ac.getBean("studentService");
studentService = ac.getBean("studentService", StudentService.class);
}
@Test
public void testSaveStudent(){
Student student = new Student(null,"不与三的毛竹",18);
studentService.saveStudent(student);
}
}
再次测试增加功能
测试删除功能
删除id为5的学生:
@Test
public void testDeleteStudent(){
studentService.deleteStudent(5);
}
结果:
测试更新功能
把id为4的学生姓名改为不与三的毛竹,年龄改为18
@Test
public void testUpdateStudent(){
Student student = new Student(4, "不与三的毛竹", 18);
studentService.updateStudent(student);
}
结果:
测试根据id查找功能
查找id为4的学生,并在控制台打印:
@Test
public void testSelectStudentById(){
Student student = studentService.selectStudentById(4);
System.out.println(student);
}
结果:
测试查询所有功能
查找所有的学生,并在控制台打印:
@Test
public void testSelectAll(){
List<Student> students = studentService.selectAll();
for (Student student : students) {
System.out.println(student);
}
}
结果:
5、分步改为纯注解开发
0、增加注解扫描
我们需要让spring去扫描所有的类,需要在配置文件增加一行:
<context:component-scan base-package="psers.buyusan"/>
根据提示补全约束:
这样程序在加载到这里的时候,spring就会扫描所有的类,去读取注解。
1、修改service
先来看目前的application-context.xml有这样几行:
<!--管理studentService,并注入studentDao-->
<bean id="studentService" class="pers.buyusan.service.StudentServiceImpl">
<property name="studentDao" ref="studentDao"/>
</bean>
它的作用是给studentServiceImpl类取名id为studentService放入Spring容器,然后把容器中id为studenDao的类注入到studentServiceImpl中的studentDao属性。
接下来我们在studentServiceImpl类中添加如下注解:
@Service的作用就是把该类的对象放入Spring,除了@Service,还可以用@Repository、@Controller、@Component,他们的功能一样,为了方便区别类所属的层,@Service一般用于业务层,@Controller一般用于表现层/控制层,@Repository一般用于持久层,而@Component用于注解其他的类型的类。
然后我们要给studentServiceImpl对象注入id为studentDao的对象,我们发现,studentDao属性和id完全一样,所以我们只需在属性上增加注解@Autowired即可。
如果bean中的id和属性名不一致,可以在@Autowired的基础上增加@Qualifier,并在@Qualifier中指定bean的id即可。
另外也可以通过@Resource来注入属性,但这个注解是jdk提供的,功能不如Spring强大,一般不做使用。
增加了@Autowired后,可以去掉set方法了。并去掉配置文件中的对应的bean标签。
改完之后我们随便测试一个方法,发现仍能运行,说明程序OK,继续下一步。
2、修改DAO
同样的把studentDaoImpl.class修改如下:
这里我们用了@Repository,因为dao属于持久层。
再次测试。通过。
如果注入的是基本类型数据,则用@Value。后面会体现。
3、把第三方类改为用注解引入
个人觉得开发中,只需要将自己编写的类用注解注入即可,第三类注入比较复杂,相比之下用配置文件的方式更容易接受,但我们的目标是全注解,所以我们还要继续。
现在配置文件里有两个bean的注入,一个是queryRunner,一个dataSource,我们知道,容器中添加bean的方式有三种,我们无法修改第三方的类,不能在上边增加@Component,所以我们要采取另一种工厂的方式,把这两个实例交给Spring。
这里我们需要创建一个额外的类JdbcConfig,通过注解把创建的对象交给Spring。
首先是queryRunner:
类:JdbcConfig,增加方法:
@Bean(name = "queryRunner")
public QueryRunner createQueryRunner(DataSource dataSource){
QueryRunner queryRunner = new QueryRunner(dataSource);
return queryRunner;
}
这里用了一个方法返回一个queryRunner,同时给这个方法加入注解@Bean,@Bean的name是该对象在容器中的id,方法的参数名是bean里的其他对象的id。
这里dataSource不能随便修改,否则会Spring会找不到容器中的dataSource。
增加方法:
@Bean(name = "dataSource")
public DataSource createDataSource(){
try {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/spring");
dataSource.setUser("root");
dataSource.setPassword("root");
return dataSource;
} catch (PropertyVetoException e) {
e.printStackTrace();
}
return null;
}
这里的@Bean中的name就是对应进入容器后的id,这里要和刚才createQueryRunner的方法的参数一致。最后,别忘了给JdbcConfig加入@Component,否则这个类不会被Spring识别到。
写到这里,配置文件的<bean>就全部消失了。
再次测试,依然通过。
4、纯注解的最后一步
现在的配置文件变成什么样了呢?
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="pers.buyusan"/>
</beans>
如果能把扫描注解的标签去掉,整个xml文件就没必要存在了。
现在分析一下:
首先,我们通过ApplicationContext ac = new ClassPathXmlApplicationContext("application-context.xml");
获取到了bean容器,这里是通过读取xml配置文件的方式,当Spring读到xml,发现xml又让Spring去扫描包,读取注解,那能不能直接告诉Spring去读注解,不用再读取配置文件了呢?当然可以。
ApplicationContext ac = new AnnotationConfigApplicationContext(XXX.class);
这样就告诉Spring去读取一个类的注解,而不是读取配置文件了。
接下来的问题是,被读取的这个类,要有什么样的特征,让Spring知道去扫描其他的注解呢?
这个类可以是任意的类,为了方便功能划分。我们把JdbcConfig改名为Config,意为此类为加载配置的类。在这个类中的@Component下增加@ComponentScan,设置basePackages为"pers.buyusan",这样,Spring就会读取其他的注解并加载和注入bean了。
这是我们把application-context.xml删除就可以了,测试类也进行修改一下:
最后进行测试,各个功能仍能正常使用。
到这里,就完成了由配置文件转型为纯注解开发。
6、进一步优化
@Qualifier的使用
在queryRunner中注入dataSource的时候我们需要参数名和bean的id一致,那么如果不一致的情况下我们可以用@Qualifier进行id指定。
在@Autowired的时候如果属性名和bean的id无法对应自动注入,也可以通过@Qualifier注解来进行手动指定。
配置信息的提取
我们发现,在装配dataSource的时候,把数据库的连接信息以硬编码的方式写死到了代码里,这违背了Spring的解耦的思想,所以Spring提供了读取配置文件的功能,同样可以用注解表示,这个注解就是@PropertySource,他的value是一个数组,指定读取哪个配置文件。
我们现在resources目录下创建一个jdbc.properties
填入配置信息:
在Config中加入@PropertySource注解
在Config类中,把数据库信息提取成成员变量,并用@value方式注入。
单元测试的整合
在maven的pom.xml文件中添加依赖,在开头环境准备的时候我们已经添加。
注意junit版本必须是4.12以上,另外spring-test的版本要与spring-context版本一致。
我们在web包下创建SpringTest类,用作新的测试类。
在类中我们写入注解@Runwith和@ContextConfiguration
package pers.buyusan.web;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import pers.buyusan.config.Config;
/**
* @author dongqg
* @Description
* @date 2019-07-13 19:49
**/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {Config.class})
public class SpringTest {
}
说明:
@Runwith:测试运行器,JUnit所有的测试方法都是由测试运行器负责执行,JUnit为单元测试提供了一个默认的测试运行器BlockJUnit4ClassRunner。
SpringJUnit4ClassRunner:运行于Spring测试环境(spring集成了Junit)。
@ContextConfiguration:
locations属性:用于指定配置文件的位置。如果是类路径下,需要用classpath:表明
classes属性:用于指定注解的类。当不使用xml配置时,需要用此属性指定注解类的位置。我们用classes属性。
这时,我们的类SpringTest就变成了测试类,我们可以直接为此类注入studentService。并加入测试方法:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {Config.class})
public class SpringTest {
@Autowired
@Qualifier("studentService")
private StudentService studentService;
@Test
public void testSelectAll(){
List<Student> students = studentService.selectAll();
for (Student student : students) {
System.out.println(student);
}
}
}
测试结果为:
总结
至此,所有工作已全部完成。这个demo我写了两次,有个坑两次都踩中了,就是Config类忘记添加@Component就直接去测试,结果报错找不到queryRunner对象。这个很容易忘记。
最后来一份终极的目录结构,与开始的有些出入,删除了一些类并加入了一些类。可能开始有些类创建了没有用到,写到后边懒得修改了,我贴一份新的。
最后,这个小案例不算难,主要用于Spring容器中bean的装配和注入的练习,并分别用了配置文件和注解的方式进行比较,正常一个小时内应该可以练习完毕整个过程。由于我一边写博客一边编码,这个案例差不多用了五六个小时的样子。经过这样的练习,我对Spring的依赖注入和注解的功能有了更深的印象。
谢谢大家。