Spring WebMvc基础-WebMvc与Dao整合的两种方式

从 0 开始深入学习 Spring 专栏目录总览

前面我们搭建好了工程,接下来我们来用一个实际的业务场景,来尽可能完整的学习 WebMvc 的部分。

0. 业务场景概述

我们在整个 WebMvc 中的讲解,使用的业务场景是比较简单的 “部门 – 用户” 模型,即部门单表,部门对用户一对多。以此来完成 CRUD 的实现。

1. SpringWebMvc整合dao【掌握】

既然要实现 CRUD ,那一定离不开数据库了。下面咱先用 SpringWebMvc 来整合 spring-jdbc 。

1.1 创建工程,添加依赖

既然是整合 dao ,咱已经再熟悉不过了吧,快速的来编写一下吧。首先来创建一个新的工程 spring-04-webmvc-xml-withdao ,并导入依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.framework.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring.framework.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.framework.version}</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>javax.servlet.jsp-api</artifactId>
        <version>2.3.3</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>

1.2 父容器的配置文件

首先我们先讲解使用 xml 配置文件的方式。配置文件中,我们只需要干这么几件事:开启事务控制、注册数据源、添加一个 JdbcTemplate ,以及准备好事务管理器即可:(spring-ioc-withdao.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" 
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans.xsd 
                           http://www.springframework.org/schema/context 
                           https://www.springframework.org/schema/context/spring-context.xsd 
                           http://www.springframework.org/schema/tx 
                           http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="com.linkedbear.spring.withdao">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring-webmvc?characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

这里面有一个细节我们来说一下。注意上面的包扫描排除,小册把所有标注了 @Controller 的类在包扫描时都排除掉了,这样做的目的我们在上一章中说过,父容器不扫描 Controller ,子容器只扫描 Controller ,这样可以保证父子容器的 bean 完整和隔离。

1.3 创建数据库和表

注意上面的配置文件中,小册换了一个数据库哈,咱用一个全新的库来讲解。创建库的 DDL 如下:

CREATE DATABASE spring-webmvc CHARACTER SET 'utf8mb4';

接下来,我们来创建两张数据库表,分别是部门表和用户表:

CREATE TABLE tbl_dept (
  id varchar(32) NOT NULL,
  name varchar(15) NOT NULL,
  tel varchar(15) DEFAULT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE tbl_user (
  id varchar(32) NOT NULL,
  username varchar(20) NOT NULL,
  name varchar(12) DEFAULT NULL,
  birthday datetime DEFAULT NULL,
  photo longblob,
  dept_id varchar(32) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

之后,给 tbl_dept 表添加两条数据:

INSERT INTO tbl_dept(id, name, tel) VALUES ('6ded6d3bdc8f4fc70bcc4347822a5ca3', 'haha', '12321');
INSERT INTO tbl_dept(id, name, tel) VALUES ('84fef077c8dd97be89b2dff7de601073', 'gege', '32123');

再给 tbl_user 表添加三条数据:

INSERT INTO tbl_user(id, username, name, birthday, photo, dept_id) VALUES ('09ec5fcea620c168936deee53a9cdcfb', 'zhangsan', '张三', '2020-01-01 20:00:00', NULL, '6ded6d3bdc8f4fc70bcc4347822a5ca3');
INSERT INTO tbl_user(id, username, name, birthday, photo, dept_id) VALUES ('31e944950285bdcec68008e404eab324', 'lisi', '李四', '2020-02-02 20:00:00', NULL, '6ded6d3bdc8f4fc70bcc4347822a5ca3');
INSERT INTO tbl_user(id, username, name, birthday, photo, dept_id) VALUES ('5d0eebc4f370f3bd959a4f7bc2456d89', 'wangwu', '王五', '2020-03-03 20:00:00', NULL, '84fef077c8dd97be89b2dff7de601073');

1.4 编写、复制基本代码

接下来,咱分别对这两个表,编写对应的实体类,以及比较简单的 service 和 dao 。

以下部分熟练的小伙伴可以直接抄代码过去,不必自己再浪费时间编写了。

1.4.1 entity

public class Department {
    
    private String id;
    
    private String name;
    
    private String tel;
    
    // getter setter equals hashcode tostring
public class User {
    
    private String id;
    
    private String username;
    
    private String name;
    
    private Date birthday;
    
    private byte[] photo;
    
    private Department department;
    
    // getter setter equals hashcode tostring

1.4.2 dao

@Repository
public class DepartmentDao {
    
    @Autowired
    JdbcTemplate jdbcTemplate;
    
    public int save(Department department) {
        return jdbcTemplate.update("insert into tbl_dept (id, name, tel) values (?, ?, ?)",
                UUID.randomUUID().toString().replaceAll("-", ""), department.getName(), department.getTel());
    }
    
    public int deleteById(String id) {
        return jdbcTemplate.update("delete from tbl_dept where id = ?", id);
    }
    
    public List<Department> findAllDepartments() {
        return jdbcTemplate.query("select * from tbl_dept", new BeanPropertyRowMapper<>(Department.class));
    }
    
    public List<Department> findDepartments(Department query) {
        StringBuilder sql = new StringBuilder();
        List<Object> params = new ArrayList<>(3);
        sql.append("select * from tbl_dept where 1 = 1");
        if (query.getId() != null) {
            sql.append(" and id = ?");
            params.add(query.getId());
        }
        if (StringUtils.hasText(query.getName())) {
            sql.append(" and name like concat('%', ?, '%')");
            params.add(query.getName());
        }
        return jdbcTemplate.query(sql.toString(), params.toArray(), new BeanPropertyRowMapper<>(Department.class));
    }
    
    public Department findById(String id) {
        List<Department> departments = jdbcTemplate
                .queryForList("select * from tbl_dept where id = ?", Department.class, id);
        return departments.size() > 0 ? departments.get(0) : null;
    }
}
@Repository
public class UserDao {
    
    @Autowired
    JdbcTemplate jdbcTemplate;
    
    public int update(User user) {
        StringBuilder sql = new StringBuilder();
        List<Object> params = new ArrayList<>(6);
        sql.append("update tbl_user set username = ?, name = ?, birthday = ?, dept_id = ?");
        params.add(user.getUsername());
        params.add(user.getName());
        params.add(user.getBirthday());
        params.add(user.getDepartment().getId());
        if (user.getPhoto() != null) {
            sql.append(", photo = ?");
            params.add(user.getPhoto());
        }
        sql.append(" where id = ?");
        params.add(user.getId());
        return jdbcTemplate.update(sql.toString(), params.toArray());
    }
    
    public List<User> findAllUsers() {
        return jdbcTemplate.query("select usr.*, dept.name as dept_name from tbl_user usr "
                + "left join tbl_dept dept on usr.dept_id = dept.id", new UserRowMapper());
    }
    
    public User findById(String id) {
        List<User> userList = jdbcTemplate.query("select usr.*, dept.name as dept_name from tbl_user usr "
                + "left join tbl_dept dept on usr.dept_id = dept.id where usr.id = ?", new UserRowMapper(), id);
        return userList.size() > 0 ? userList.get(0) : null;
    }
    
    // 由于用到了关联查询,BeanPropertyRowMapper无法胜任工作,我们自己定义一个吧
    public static class UserRowMapper implements RowMapper<User> {
    
        LobHandler lobHandler = new DefaultLobHandler();
    
        @Override
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            User user = new User();
            user.setId(rs.getString("id"));
            user.setUsername(rs.getString("username"));
            user.setName(rs.getString("name"));
            user.setBirthday(rs.getDate("birthday"));
            user.setPhoto(lobHandler.getBlobAsBytes(rs, "photo"));
            Department department = new Department();
            department.setId(rs.getString("dept_id"));
            department.setName(rs.getString("dept_name"));
            user.setDepartment(department);
            return user;
        }
    }
}

1.4.3 service

@Service
@Transactional(rollbackFor = Exception.class)
public class DepartmentService {
    
    @Autowired
    DepartmentDao departmentDao;
    
    public void save(Department department) {
        departmentDao.save(department);
    }
    
    public void deleteById(String id) {
        departmentDao.deleteById(id);
    }
    
    public List<Department> findDepartmentsByName(String name) {
        Department query = new Department();
        query.setName(name);
        return departmentDao.findDepartments(query);
    }
    
    public List<Department> findDepartments(Department query) {
        if (query == null) {
            return departmentDao.findAllDepartments();
        }
        return departmentDao.findDepartments(query);
    }
    
    public Department findById(String id) {
        return departmentDao.findById(id);
    }
}
@Service
@Transactional(rollbackFor = Exception.class)
public class UserService {
    
    @Autowired
    UserDao userDao;
    
    public void update(User user) {
        userDao.update(user);
    }
    
    public List<User> findAllUsers() {
        return userDao.findAllUsers();
    }
    
    public User findById(String id) {
        return userDao.findById(id);
    }
}

1.4.4 搬运代码

从 spring-04-webmvc 中,把 /WEB-INF/pages 整个目录复制粘贴过来,这样咱暂时就不用再写页面了。

这样所有的基本代码就都写完了。

1.5 编写基本的DepartmentController

下面咱先写一个最最简单的 DepartmentController ,这里面啥也不用干,先把 DepartmentService 注入进来,打印一下就行:

@Controller
public class DepartmentController {
    
    @Autowired
    DepartmentService departmentService;
    
    @RequestMapping("/department/demo")
    public String demo() {
        System.out.println(departmentService);
        return "demo";
    }
}

1.6 WebMvc的配置文件

下面我们要编写 SpringWebMvc 的配置文件了。(spring-mvc-withdao.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 
                           http://www.springframework.org/schema/beans/spring-beans.xsd 
                           http://www.springframework.org/schema/context 
                           https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.linkedbear.spring.withdao.controller"/>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

这里的包扫描,可以直接扫描 @Controller ,也可以扫整包,然后只留下 @Controller 注解即可:(注意要禁用默认扫描过滤规则)

<context:component-scan base-package="com.linkedbear.spring.withdao" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

两种方法任选其一即可。

1.7 web.xml配置

web.xml 中,需要分别配置全局 IOC 容器的初始化监听器,以及 SpringWebMvc 中 DispatcherServlet 的配置文件加载,以此来形成父子容器:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-ioc-withdao.xml</param-value>
    </context-param>
    
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc-withdao.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

1.8 测试运行

一切就绪之后,部署应用,启动 Tomcat ,并访问 http://localhost:8080/spring-webmvc/department/demo ,浏览器可以跳转到 demo 页面,控制台也可以打印 DepartmentService ,说明整合已经没有问题了。

com.linkedbear.spring.withdao.service.DepartmentService@64e0321

2. 注解驱动环境整合【掌握】

接下来,我们继续使用注解驱动的方式,来整合 webmvc 和 dao 。

2.1 创建工程,添加依赖

同样的,跟上面一样,我们再创建一个 spring-04-webmvc-anno-withdao 工程,并添加上面一样的依赖。

2.2 搬运代码

把上面编写的所有 src/java 下的代码,全部搬过来;把 /WEB-INF/pages 整个目录也复制粘贴过来。

搬运完成后的代码结构应该是这样的:

Spring WebMvc基础-WebMvc与Dao整合的两种方式

2.3 编写配置类

上一章中我们也学习了如何借助 Servlet 3.0 规范的写法,整合 SpringWebMvc ,所以也就很清楚应该怎么编写配置类了。

2.3.1 父容器的配置类

首先咱先来写父容器的配置类:

@Configuration
@Import(JdbcDaoConfiguration.class)
@ComponentScan(value = "com.linkedbear.spring.withdao",
               excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class),
                       @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Configuration.class)
               })
public class RootConfiguration {
    
}

注意这里的包扫描过滤规则,小册除了将 @Controller 注解排除之外,还把 @Configuration 也排除了。这样写,是为了能把下面 WebMvc 的配置类也写在 RootConfiguration 同一个包下。当然,如果小伙伴不愿意这样写的话也没关系,只需要把接下来的 WebMvc 配置类拿到 com.linkedbear.spring.withdao 包外,让包扫描扫不到它就行。

还有一个细节,这里小册做了一点额外的手脚,我把所有有关 dao 的配置,都放到一个新的配置类中。这里面的内容也是简单的很,小伙伴们直接复制粘贴即可。

@Configuration
@EnableTransactionManagement
public class JdbcDaoConfiguration {
    
    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring-webmvc?characterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }
    
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    
    @Bean
    public TransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

2.3.2 WebMvc的配置类

接下来是 WebMvc 的配置类,这里面有两种写法,我们先来写比较简单的第一种。

第一种写法,只需要注册一个 ViewResolver 就可以了,然后就是配置包扫描(注意禁用默认的扫描规则):

@Configuration
@ComponentScan(value = "com.linkedbear.spring.withdao",
               includeFilters = @ComponentScan.Filter(value = Controller.class, type = FilterType.ANNOTATION),
               useDefaultFilters = false)
public class WebMvcConfiguration {
    
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/pages/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }
}

第二种写法更简单,而且它也是以后常用的编写方式。咱下面分步解释。

2.3.3 @EnableWebMvc的配置类写法

开启 WebMvc 的配置,其实只需要一个注解即可:@EnableWebMvc ,emmm 没错它又是模块装配了。所以我们可以来写一个新的配置类,上面的我们就不用了。

@Configuration
@EnableWebMvc
@ComponentScan(value = "com.linkedbear.spring.withdao",
               includeFilters = @ComponentScan.Filter(value = Controller.class, type = FilterType.ANNOTATION),
               useDefaultFilters = false)
public class EnableWebMvcConfiguration {

}

这样开启了之后,还是要配置 ViewResolver 的,不然 SpringWebMvc 也不知道我们用的什么视图解析。要配置 ViewResolver ,仍然可以使用 @Bean 的方式,但如果这么写的话,就跟上面没什么两样了。SpringWebMvc 给我们提供了一个 WebMvcConfigurer 接口,可以用这个接口来实现编程式配置。

所以接下来,我们直接让 EnableWebMvcConfiguration 实现 WebMvcConfigurer 接口即可:

public class EnableWebMvcConfiguration implements WebMvcConfigurer {

实现了接口之后,接下来要重写里面的一个方法:configureViewResolvers ,用这个方法,可以快速配置基于 jsp 的视图解析:

public class EnableWebMvcConfiguration implements WebMvcConfigurer {
    
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/pages/", ".jsp");
    }
}

看,这样是不是写起来就简单的很了?包括后面我们再学其他配置的时候,用这种方式也比上一种简单。

2.4 编写WebApplicationInitializer

最后的 WebApplicationInitializer ,编写起来也是很简单了吧,快速写一下吧:

public class WebMvcApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {RootConfiguration.class};
    }
    
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] {EnableWebMvcConfiguration.class};
    }
    
    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }
}

2.5 测试运行

重新部署好当前工程,然后启动 Tomcat ,浏览器访问 http://localhost:8080/spring-webmvc/department/demo ,发现仍然可以跳转到 demo 页面,说明使用注解驱动的环境整合也是没有问题的。

到这里我们就算把 WebMvc 跟 dao 层都整合好了,除了这些之外,还有一些跟 dao 框架的整合(MyBatis 、SpringData 、Hibernate 等),这些我们放到后面的整合篇介绍。

3. 页面准备

接下来我们来准备一下简单页面。这部分内容小伙伴可以直接从 GitHub 仓库中抄过来,不必再手工编写。

该部分源码位置,可从 spring-04-webmvc-xml-withdao 工程的 src/main/webapp/pages 中找到。

 

免责声明:
1.本站所有内容由本站原创、网络转载、消息撰写、网友投稿等几部分组成。
2.本站原创文字内容若未经特别声明,则遵循协议CC3.0共享协议,转载请务必注明原文链接。
3.本站部分来源于网络转载的文章信息是出于传递更多信息之目的,不意味着赞同其观点。
4.本站所有源码与软件均为原作者提供,仅供学习和研究使用。
5.如您对本网站的相关版权有任何异议,或者认为侵犯了您的合法权益,请及时通知我们处理。
火焰兔 » Spring WebMvc基础-WebMvc与Dao整合的两种方式