首页 > 编程学习 > SSM 综合案例

SSM 综合案例

发布时间:2022/10/1 1:41:24

需求

完成部门基本的 CRUD 和分页查询,完成员工基本的 CRUD、分页查询和过滤查询(根据姓名和邮箱模糊查询,根据部门查询)。

技术架构

使用 Spring MVC + Spring(AOP TX) + MyBatis,数据库选用 MySQL,视图选用 JSP。

SSM 集成作用及本质

作用:在框架上基础上开发,发挥各个框架在各层的好处,提高开发效率。

本质:

  • 利用MyBatis做持久化
  • 使用 AOP 来配置事务;
  • 使用 Spring MVC 解决 MVC 的问题,处理请求和响应。
  • Spring 去集成 Spring MVC 和 MyBatis,即控制器对象、业务对象、Mapper 对象等都交由 Spring 容器管理,使用 Spring IoC 和 DI 来完成对象创建及其属性注入

集成步骤

  • 先用 Spring 集成 MyBatis

    • 搭建项目,添加依赖配置插件。
    • 把 Spring 和 MyBatis 的等配置文件拷贝进项目 resources 目录下
    • 配置数据库链接池
    • 配置 SqlSessionFactory
    • 配置 Mapper 对象
    • 配置业务对象
    • 配置事务相关
  • 再加入 Spring MVC

    • 在 web.xml 配置前端控制器和编码过滤器
    • 在项目 resources 目录下添加 Spring MVC 的配置文件,并配置 MVC 注解解析器,扫描控制器,静态资源处理,视图解析器等等
    • 在 Spring MVC 的配置文件中引入 Spring 配置文件

检查依赖和插件

MyBatis 逆向工程

一个 Maven 插件,根据数据的表生成实体类和 Mapper 接口和 Mapper XML。

插件使用

新建表

参考文档,在 ssm 库新建如下表:

CREATE TABLE `department` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `sn` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

配置插件

在 pom.xml,配置 MyBatis 逆向工程插件:

<!-- MyBatis 逆向工程插件 -->
<plugin>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <version>1.3.2</version>
    <configuration>
        <verbose>true</verbose>
        <overwrite>false</overwrite>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.45</version>
        </dependency>
    </dependencies>
</plugin>

插件设置

在项目中 resources 目录中新建配置文件 generatorConfig.xml,在 generatorConfig.xml 配置数据库连接、配置表等等。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
		PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
		"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!-- 配置生成器 -->
<generatorConfiguration>

	<context id="mysql" defaultModelType="hierarchical"
			 targetRuntime="MyBatis3Simple">

		<!-- 自动识别数据库关键字,默认false,如果设置为true,根据SqlReservedWords中定义的关键字列表; 一般保留默认值,遇到数据库关键字(Java关键字),使用columnOverride覆盖 -->
		<property name="autoDelimitKeywords" value="false" />
		<!-- 生成的Java文件的编码 -->
		<property name="javaFileEncoding" value="UTF-8" />
		<!-- 格式化java代码 -->
		<property name="javaFormatter"
				  value="org.mybatis.generator.api.dom.DefaultJavaFormatter" />
		<!-- 格式化XML代码 -->
		<property name="xmlFormatter"
				  value="org.mybatis.generator.api.dom.DefaultXmlFormatter" />

		<!-- beginningDelimiter和endingDelimiter:指明数据库的用于标记数据库对象名的符号,比如ORACLE就是双引号,MYSQL默认是`反引号; -->
		<property name="beginningDelimiter" value="`" />
		<property name="endingDelimiter" value="`" />

		<commentGenerator>
			<property name="suppressDate" value="true" />
			<property name="suppressAllComments" value="true" />
		</commentGenerator>

		<!-- 必须要有的,使用这个配置链接数据库 -->
		<jdbcConnection driverClass="com.mysql.jdbc.Driver"
						connectionURL="jdbc:mysql:///ssm" userId="root" password="admin">
			<!-- 这里面可以设置property属性,每一个property属性都设置到配置的Driver上 -->
		</jdbcConnection>

		<!-- java类型处理器 用于处理DB中的类型到Java中的类型,默认使用JavaTypeResolverDefaultImpl; 注意一点,默认会先尝试使用Integer,Long,Short等来对应DECIMAL和 
			NUMERIC数据类型; -->
		<javaTypeResolver
				type="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl">
			<!-- true:使用BigDecimal对应DECIMAL和 NUMERIC数据类型 false:默认, scale>0;length>18:使用BigDecimal; 
				scale=0;length[10,18]:使用Long; scale=0;length[5,9]:使用Integer; scale=0;length<5:使用Short; -->
			<property name="forceBigDecimals" value="false" />
		</javaTypeResolver>


		<!-- java模型创建器,是必须要的元素 负责:1,key类(见context的defaultModelType);2,java类;3,查询类 
			targetPackage:生成的类要放的包,真实的包受enableSubPackages属性控制; targetProject:目标项目,指定一个存在的目录下,生成的内容会放到指定目录中,如果目录不存在,MBG不会自动建目录 -->
		<javaModelGenerator targetPackage="cn.XXX.domain"
							targetProject="src/main/java">
			<!-- for MyBatis3/MyBatis3Simple 自动为每一个生成的类创建一个构造方法,构造方法包含了所有的field;而不是使用setter; -->
			<property name="constructorBased" value="false" />

			<!-- for MyBatis3 / MyBatis3Simple 是否创建一个不可变的类,如果为true, 那么MBG会创建一个没有setter方法的类,取而代之的是类似constructorBased的类 -->
			<property name="immutable" value="false" />

			<!-- 设置是否在getter方法中,对String类型字段调用trim()方法
			<property name="trimStrings" value="true" /> -->
		</javaModelGenerator>

		<!-- 生成SQL map的XML文件生成器, 注意,在Mybatis3之后,我们可以使用mapper.xml文件+Mapper接口(或者不用mapper接口),
			或者只使用Mapper接口+Annotation,所以,如果 javaClientGenerator配置中配置了需要生成XML的话,这个元素就必须配置
			targetPackage/targetProject:同javaModelGenerator -->
		<sqlMapGenerator targetPackage="cn.XXX.mapper"
						 targetProject="src/main/resources">
			<!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false -->
			<property name="enableSubPackages" value="true" />
		</sqlMapGenerator>


		<!-- 对于mybatis来说,即生成Mapper接口,注意,如果没有配置该元素,那么默认不会生成Mapper接口 targetPackage/targetProject:同javaModelGenerator
			type:选择怎么生成mapper接口(在MyBatis3/MyBatis3Simple下): 1,ANNOTATEDMAPPER:会生成使用Mapper接口+Annotation的方式创建(SQL生成在annotation中),不会生成对应的XML;
			2,MIXEDMAPPER:使用混合配置,会生成Mapper接口,并适当添加合适的Annotation,但是XML会生成在XML中; 3,XMLMAPPER:会生成Mapper接口,接口完全依赖XML;
			注意,如果context是MyBatis3Simple:只支持ANNOTATEDMAPPER和XMLMAPPER -->
		<javaClientGenerator targetPackage="cn.XXX.mapper"
							 type="XMLMAPPER" targetProject="src/main/java">
			<!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false -->
			<property name="enableSubPackages" value="true" />

			<!-- 可以为所有生成的接口添加一个父接口,但是MBG只负责生成,不负责检查 <property name="rootInterface"
				value=""/> -->
		</javaClientGenerator>

		<!-- 要生成实体类,接口,xml的表,可多个表一起生成 -->
		<table tableName="department">
			<property name="useActualColumnNames" value="true"/>
			<property name="constructorBased" value="false" />
			<generatedKey column="id" sqlStatement="JDBC" />
		</table>
		<table tableName="department2">
			<property name="useActualColumnNames" value="true"/>
			<property name="constructorBased" value="false" />
			<generatedKey column="id" sqlStatement="JDBC" />
		</table>
	</context>
</generatorConfiguration>

修改生成的jar包

现在生成的xml文件中会有很多不必要的参数类型声明, 可拷贝修改过的jar包将本地仓库的同名jar包替换

运行插件

在 IDEA 的 Maven 窗口下中双击 mybatis-generator:generate 即可运行,运行注意观察控制台打印。
在这里插入图片描述

生成的效果

按对应数据库,对应表的列生成实体类,接口和xml 里面有基本的增删改查方法

Spring 集成 MyBatis

配置数据库连接池

在 resources 目录下新建 applicationContext.xml,配置如下:

<!-- 关联 db.properties 文件 -->
<context:property-placeholder location="classpath:db.properties"/>

<!-- 配置 DataSource bean -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

配置 SqlSessionFactory bean

在 applicationContext.xml,配置如下:

<!-- 配置 SqlSessionFactory bean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 关联主配置文件 目前可以不配置-->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <!-- 配置别名 若不用别名,可以不配置 -->
    <property name="typeAliasesPackage" value="cn.xxx.domain"/>
    <!-- 配置数据源-->
    <property name="dataSource" ref="dataSource"/>
    <!-- 关联 Mapper XML 可以不配置, 前提编译 Mapper 接口字节码文件与 Mapper XML 文件在一起,文件名也一样 -->
</bean>

配置 Mapper bean

在 applicationContext.xml,配置如下:

<!-- 配置 Mapper bean -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!-- 指定 Mapper 接口所在包 -->
    <property name="basePackage" value="cn.xxx.mapper"/>
</bean>

配置 Service bean

修改 DepartmentServiceImpl,贴 IoC 和 DI 注解。
在 applicationContext.xml,配置如下:

<!-- 配置 IoC DI 注解解析器 , 让 Spring 帮我们创建业务接口的实现类对象, 完成属性或者字段注入 -->
<context:component-scan base-package="cn.xxx.service.impl"/>

配置事务相关

在 applicationContext.xml,配置如下:

<!-- 配置事务管理器 WHAT-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置增强,包含 WHEN,并关联上面 WHAT-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="get*" read-only="true"/>
        <tx:method name="select*" read-only="true"/>
        <tx:method name="query*" read-only="true"/>
        <tx:method name="count*" read-only="true"/>
        <tx:method name="list*" read-only="true"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

<!-- 配置 AOP -->
<aop:config>
    <!-- WHERE -->
    <aop:pointcut id="txPointcut" expression="execution(* cn.wolfcode.service.impl.*ServiceImpl.*(..))"/>
    <!-- 关联 WHERE WHEN-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class DepartmentServiceTest {
    @Autowired
    private IDepartmentService departmentService;

    @Test
    public void testSave() {
        Department department = new Department();
        department.setName("公关部");
        department.setSn("DC");
        departmentService.save(department);
    }
}

集成 Spring MVC

在 web.xml 配置端控制器和编码过滤器

<!-- 配置前端控制器 -->
<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:mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<!-- 配置编码过滤器, 针对 POST -->
<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

添加 Spring MVC 的配置文件

在 resources 目录新建 mvc.xml,配置如下:

<!-- IoC DI 注解解析器,配置扫描控制器。让 Spring 帮我们创建控制器对象,并注入 -->
<context:component-scan base-package="cn.xxx.web.controller"/>

<!-- 配置 MVC 注解解析器,请求路径注解,时间注解,JSON 注解等生效 -->
<mvc:annotation-driven/>

<!-- 处理静态资源 -->
<mvc:default-servlet-handler/> 

<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
</bean>

在 Spring MVC 的配置文件中引入 Spring 配置文件

在 mvc.xml , 让两个配置文件一起加载 ,配置如下

<!-- 引入 applicationContext.xml -->
<import resource="classpath:applicationContext.xml"/>

编写 DepartmentController

@Controller
@RequestMapping("/department")
public class DepartmentController {
    @Autowired
    private IDepartmentService departmentService;

    // 处理查询所有部门请求
    @RequestMapping("/list")
    public String list(Model model){
        List<Department> departments = departmentService.listAll(qo);
        model.addAttribute("departments", departments);
        return "department/list"; // 找视图 /WEB-INF/views/department/list.jsp
    }

    // 处理删除请求
    @RequestMapping("/delete")
    public String delete(Long id){
        if (id != null) {
            departmentService.delete(id);
        }
        return "redirect:/department/list"; // 让浏览器重新发一次请求
    }

    // 去新增或去修改页面
    @RequestMapping("/input")
    public String input(Long id, Model model){
        if (id != null) { // 表示去修改
            Department department = departmentService.get(id);
            model.addAttribute("department", department);
        }
        return "department/input"; // 让浏览器重新发一次请求
    }

    // 新增或修改
    @RequestMapping("/saveOrUpdate")
    public String saveOrUpdate(Department department){
        if(department.getId()  == null){ // 新增
            departmentService.save(department);
        }else {
            departmentService.update(department); // 修改
        }
        return "redirect:/department/list"; // 让浏览器重新发一次请求
    }
}

部门分页查询

编写封装分页参数类

@Setter
@Getter
public class QueryObject {
    // 当前页
    private int currentPage = 1;
    // 每页条数
    private int pageSize = 5;
    public int getStart(){
        return (currentPage - 1) * pageSize;
    }
}

编写封装查询结果类

@Getter
public class PageResult<T> {

    private int currentPage; // 页面传的
    private int pageSize; // 页面传的
    private List<T> data; // 数据库查询
    private int totalCount; // 数据库查询

    private int totalPage; // 总页数
    private int prevPage; // 上一页
    private int nextPage; // 下一页

    public PageResult(int currentPage, int pageSize, int totalCount, List<T> data) {
        this.currentPage = currentPage;
        this.pageSize = pageSize;
        this.data = data;
        this.totalCount = totalCount;

        // 加入判断,节省计算性能
        if(totalCount <= pageSize) {
            this.totalPage = 1;
            this.prevPage = 1;
            this.nextPage = 1;
            return;
        }

        this.totalPage = this.totalCount % this.pageSize == 0
            ? this.totalCount / this.pageSize : this.totalCount / this.pageSize + 1;
        this.nextPage = this.currentPage + 1 <= this.totalPage ? this.currentPage + 1 : this.totalPage;
        this.prevPage = this.currentPage - 1 >= 1 ? this.currentPage - 1 : 1;
    }
}

给部门业务接口及实现添加分页功能

public interface IDepartmentService {
    // ......
    PageResult<Department> query(QueryObject qo);
}
@Service
public class DepartmentServiceImpl implements IDepartmentService {

    @Autowired
    private DepartmentMapper departmentMapper;

    // ......

    @Override
    public PageResult<Department> query(QueryObject qo) {
        int count = departmentMapper.selectForCount(qo);
        if(count == 0){
            return  new PageResult<>(qo.getCurrentPage(), qo.getPageSize(),
                count, Collections.emptyList());
        }
        List<Department> departments = departmentMapper.selectForList(qo);
        return  new PageResult<>(qo.getCurrentPage(), qo.getPageSize(),
            count, departments);
    }
}

给部门 Mapper 接口及 Mapper XMl 添加分页功能

public interface DepartmentMapper {
    // ......

    int selectForCount(QueryObject qo);
    List<Department> selectForList(QueryObject qo);
}
<select id="selectForCount" resultType="java.lang.Integer">
    SELECT count(*)
    FROM department
</select>
<select id="selectForList" resultType="cn.xxx.domain.Department">
    SELECT id, name, sn
    FROM department
    LIMIT #{start}, #{pageSize}
</select>

修改部门控制器查询方法

@Controller
@RequestMapping("/department")
public class DepartmentController {
    @Autowired
    private IDepartmentService departmentService;

    // 处理查询所有部门请求
    @RequestMapping("/list")
    public String list(Model model, QueryObject qo){
        PageResult<Department> pageResult = departmentService.query(qo);
        model.addAttribute("pageResult", pageResult);
        return "department/list"; // 找视图 /WEB-INF/views/department/list.jsp
    }

    // ......
}

书写jsp页面和业务相关逻辑

Copyright © 2010-2022 mfbz.cn 版权所有 |关于我们| 联系方式|豫ICP备15888888号