SSM整合及使用

news2024/12/25 9:30:54

SSM整合

介绍

SSM(Spring+SpringMVC+MyBatis)框架集由Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的部分内容),常作为数据源较简单的web项目的框架。

步骤

  1. SSM整合主要是将各个框架的核心组件都交给spring ioc容器管理
  2. SSM整合的位置在web.xml中体现
    2.1 spring-mvc.xml
    a. 视图解析器
    b. mvc注解驱动(消息转换器:字符串类型的消息转换器,JSON格式的消息转换器)
    c. 组件扫描(主要扫描控制器在哪里)
    2.2 spring-mybatis.xml
    a. 数据源(DruidDataSource, HikariDataSource)
    b. SqlSessionFactory配置
    c. Mapper接口扫描器配置
    d. 数据源事务管理器配置
    e. 事务的注解驱动
    f. 组件扫描(主要扫描业务层在哪里)

pom.xml配置

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qf.SSM</groupId>
<artifactId>Day55_SSM</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<name>Day55_SSM Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
 <maven.compiler.source>8</maven.compiler.source>
 <maven.compiler.target>8</maven.compiler.target>
 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 <spring.version>5.3.10</spring.version>
</properties>
<dependencies>
 <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context-support</artifactId>
   <version>${spring.version}</version>
 </dependency>
 <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-jdbc</artifactId>
   <version>${spring.version}</version>
 </dependency>
 <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>${spring.version}</version>
 </dependency>
 <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>8.0.28</version>
 </dependency>
 <dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis</artifactId>
   <version>3.5.13</version>
 </dependency>
 <dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis-spring</artifactId>
   <version>2.0.6</version>
 </dependency>
 <dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <version>1.18.26</version>
 </dependency>
 <dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>2.0.42</version>
 </dependency>
 <dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid</artifactId>
   <version>1.2.6</version>
 </dependency>
 <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.13</version>
   <scope>test</scope>
 </dependency>
 <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>javax.servlet-api</artifactId>
   <version>4.0.1</version>
   <scope>provided</scope>
 </dependency>
</dependencies>
<build>
 <finalName>Day55_SSM</finalName>
</build>
</project>

spring-mvc.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:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    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/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">


 <!--配置视图解析器-->
 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/" p:suffix=".jsp"/>
 <!--配置消息转换器-->
 <bean class="org.springframework.http.converter.StringHttpMessageConverter" id="stringHttpMessageConverter"/>
 <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter" id="fastJsonHttpMessageConverter">
     <property name="supportedMediaTypes">
         <list>
             <value>text/html;charset=UTF-8</value>
             <value>application/json;charset=UTF-8</value>
         </list>
     </property>
 </bean>
 <!--开启注解驱动-->
 <mvc:annotation-driven>
     <mvc:message-converters>
         <ref bean="stringHttpMessageConverter"/>
         <ref bean="fastJsonHttpMessageConverter"/>
     </mvc:message-converters>
 </mvc:annotation-driven>

 <!--扫描-->
 <context:component-scan base-package="com.qf.controller"/>
</beans>

spring-mtbatis.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
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">


 <!--配置数据源-->
 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
     <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
     <property name="url" value="jdbc:mysql://localhost:3306/lesson?serverTimezone=Asia/Shanghai"/>
     <property name="username" value="root"/>
     <property name="password" value="123456"/>
 </bean>
 <!--日志打印-->
 <bean id="configuration" class="org.apache.ibatis.session.Configuration">
     <property name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl"/>
 </bean>

 <!--配置sqlSessionFactory-->
 <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
     <property name="dataSource" ref="dataSource"/>
     <property name="mapperLocations" value="classpath:mapper/*Mapper.xml"/>
     <property name="configuration" ref="configuration"/>
     <property name="typeAliasesPackage" value="com.qf.pojo"/>
 </bean>
 <!--配置Mapper接口文件-->
 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
     <property name="basePackage" value="com.qf.mapper"/>
     <property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
 </bean>
 <!--配置事务管理-->
 <bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <property name="dataSource" ref="dataSource"/>
 </bean>
 <!--开启注解驱动-->
 <tx:annotation-driven transaction-manager="tm"/>
 <!--扫描-->
 <context:component-scan base-package="com.qf.service"/>
</beans>

web.xml配置

<?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_4_0.xsd"
      version="4.0">
 <display-name>Archetype Created Web Application</display-name>

 <context-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>classpath:spring-mybatis.xml</param-value>
 </context-param>
 <listener>
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
 <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.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>
 <filter>
     <filter-name>encodingFilter</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>
     <init-param>
         <param-name>forceEncoding</param-name>
         <param-value>true</param-value>
     </init-param>
 </filter>
 <filter-mapping>
     <filter-name>encodingFilter</filter-name>
     <url-pattern>/*</url-pattern>
 </filter-mapping>
</web-app>

至此,整合完毕,关键在于web.xml文件中,利用 Jsp工作原理进行整合,在 Jsp 中,ServletContextListener 能够监听上下文参数配置,并对该配置做出相应的处理。这个监听器可以用来配置 Mybatis 的相关信息,完成 Mybatis 的整合。 Servlet 的运作需要在 web.xml 中进行配置,在配置 Servlet 时,可以配置该 Servlet 的初始化参数。因此可以用来完成 Spring MVC 的整合。所以,SSM 框架整合的场所就是在web.xml 中。

测试

从数据库中查询user表的数据

pojo中user类:

package com.qf.pojo;

import lombok.Data;

@Data
public class User {
 private String username;
 private String password;
 private String name;
}

mapper层:

public interface UserMapper {
 List<User> getUsers();
}

service层:

public interface UserService {

 List<User> getUsers();
}

//---------------------------------------------------------------


@Service
public class UserServiceImpl implements UserService {
 @Autowired
 private UserMapper userMapper;

 @Transactional(readOnly = true,isolation = Isolation.READ_COMMITTED)
 @Override
 public List<User> getUsers() {
     return userMapper.getUsers();
 }
}

注:@Service//这个注解会创建一个bean对象放入ioc容器;

@Autowired//这个注解能够从ioc容器中找到类型匹配的实例注入值,但是前提必须ioc容器中有实例,所以必须有@Service注解;

@Transactional(readOnly = true,isolation = Isolation.READ_COMMITTED)这个注解表示事务的,将事务设置为只读,能够提升查询效率,同时设置了隔离级别为读已提交,避免脏读。为什么在service层设置事务而不是mapper层?-因为service层设置可以根据业务逻辑将事务中包含多个操作,符合事务的一致性

controller层:

@RestController
@RequestMapping("/user")
public class UserController {
 @Autowired
 private UserService userService;


 @GetMapping
 public List<User> searchUsers(){
     return userService.getUsers();
 }

}

userMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.mapper.UserMapper">
 <select id="getUsers" resultType="User">
     select * from user
 </select>
</mapper>

===========================================================================

拦截器

自定义拦截器需要实现HandlerInterceptor接口,此接口底层如下:

public interface HandlerInterceptor {
 //前置拦截,在控制器执行之前做事
 default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
     return true;
 }
	//后置拦截,在控制器执行之后做事
 default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
 }
	//在视图被渲染后做事
 default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
 }
}

应用-登录超时

当从请求中获取的username不存在时,无法登录,即登录超时,这个功能在控制器执行之前完成

package com.qf.com.qf;


import com.alibaba.fastjson.JSONObject;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.PrintWriter;

public class TimeoutInterceptor implements HandlerInterceptor {

 @Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
     HttpSession session = request.getSession();
     Object username = session.getAttribute("username");
     if (username == null) {
         //响应内容设置,APPLICATION_JSON_VALUE = application/json
         response.setContentType(MediaType.APPLICATION_JSON_VALUE);
         PrintWriter writer = response.getWriter();
         JSONObject json = new JSONObject();
         json.put("msg", "登录超时");
         json.put("status", 400);
         writer.write(json.toString());
         writer.flush();
         writer.close();
         return false;
     }
     return true;
 }
}

spring-mvc.xml文件中配置拦截器使其生效:

<mvc:interceptors>
 <mvc:interceptor>
     <!--拦截的URL地址-->
     <mvc:mapping path="/**"/>
     <!--不拦截的URL地址-->
     <mvc:exclude-mapping path="/user/login"/>
     <!--使用的拦截器-->
     <bean class="com.qf.interceptor.TimeoutInterceptor"/>
 </mvc:interceptor>
</mvc:interceptors>

测试:从前端传入JSON格式的数据登录

mapper层:

User getUserByUsername(@Param("username")String username);

userMapper.xml:

<select id="getUserByUsername" resultType="user">
     select * from user where username=#{username}
 </select>

service层:

int getUser(User user, HttpSession session);
//------------------------------------------
@Transactional(readOnly = true,isolation = Isolation.READ_COMMITTED)
@Override
public int getUser(User user, HttpSession session) {
 User dbUser = userMapper.getUserByUsername(user.getUsername());
 if(dbUser==null){
     return -1;
 }
 if(dbUser.getPassword().equals(user.getPassword())){
     session.setAttribute("username",user.getUsername());
     return 1;
 }
 return 0;
}

controller层:

@PostMapping("/login")
public int login(@RequestBody User user, HttpSession session){
 return userService.getUser(user,session);
}

当还没有进行登录时,访问user页面会报错,报错信息为拦截器设置的登录超时,状态码为400,当使用postman发送post请求,传入正确的json数据后,如{

“username”:“zs”,

“password”:“123123”

},再次访问user页面就可以正常访问了。

应用-权限检测

根据RBAC权限模型在数据库中查询用户拥有哪些权限,如果请求地址和权限匹配则放行,不匹配则返回false。此功能在控制器执行之前完成。

package com.qf.com.qf.interceptor;

import com.alibaba.fastjson.JSONObject;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class PermissionInterceptor implements HandlerInterceptor {
 @Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
     String requestURI = request.getRequestURI();
     String contextPath = request.getContextPath();
     String requestUrl = requestURI.replace(contextPath, "");
     //从数据库中查询用户的权限,返回权限的集合,这里手动创建权限集合
     List<String> accessUrls = Arrays.asList("/user/register");
     if(accessUrls.contains(requestUrl)){
         return true;
     }else {
         response.setContentType(MediaType.APPLICATION_JSON_VALUE);
         PrintWriter writer = response.getWriter();
         JSONObject json = new JSONObject();
         json.put("msg","没有该权限");
         json.put("status",400);
         writer.write(json.toString());
         writer.flush();
         writer.close();
         return false;
     }
 }
}

spring-mvc.xml配置使其生效:

<mvc:interceptors>
     <!--...其他拦截器...-->

     <mvc:interceptor>
         <mvc:mapping path="/**"/>
         <mvc:exclude-mapping path="/user/login"/>
         <bean class="com.qf.interceptor.PermissionInterceptor"/>
     </mvc:interceptor>
 </mvc:interceptors>

测试:

这时访问user界面就会报错,信息为没有访问权限,因为在拦截器中只设置了"/user/register"的访问权限。

拦截器执行流程

在这里插入图片描述

===========================================================================

文件的上传和下载

文件上传

导入依赖:

<dependency>
   <groupId>commons-fileupload</groupId>
   <artifactId>commons-fileupload</artifactId>
   <version>1.4</version>
 </dependency>

spring-mvc.xml中配置支撑

<!--文件上传下载的配置,这个bean的id值必须是multipartResolver-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
 <!--设置默认编码-->
 <property name="defaultEncoding" value="UTF-8"/>
 <!--内存中可用大小:3 * 1024 * 1024 = 3145728 = 3M-->
 <property name="maxInMemorySize" value="3145728"/>
 <!--设置临时存储目录,当上传文件大小超过内存中可使用大小时将产生临时文件-->
 <property name="uploadTempDir" value="/upload"/>
 <!--50 * 1024 * 1024 = 52428800 = 50M -->
 <!--最大上传大小-->
 <property name="maxUploadSize" value="52428800"/>
 <!--单个文件大小-->
 <property name="maxUploadSizePerFile" value="5242880"/>
</bean>

测试:保存用户信息,其中包括头像。

数据库中user表新增head_icon字段

修改pojo包中的user类:

@Data
public class User {
 private String username;
 private String password;
 private String name;
 private String headIcon;
}

mapper接口:

int saveUser(@Param("user") User user);
//--------------------------------------------------------------

	
//保存文件位置
private static final String SAVE_DIR = "e:/users/icon";
//rollbackFor表示一旦这个方法出现了异常,就需要进行数据的回滚操作
@Transactional(rollbackFor = Exception.class,isolation = Isolation.REPEATABLE_READ)
@Override
public int addUser(User user, MultipartFile file) {
 User dbUser = userMapper.getUserByUsername(user.getUsername());
 if(dbUser!=null) return -1;
 //获取文件的原始名称
 String originalFilename = file.getOriginalFilename();
 int index = originalFilename.lastIndexOf('.');
 String extension = "";//文件的扩展名
 if(index>0){
     extension = originalFilename.substring(index);
 }
 String saveFileName = user.getUsername() + extension;
 File folder = new File(SAVE_DIR);
 if(!folder.exists()){
     folder.mkdirs();
 }
 try {
     //将文件传输至给定的位置保存
     file.transferTo(new File(folder,saveFileName));
     user.setHeadIcon(saveFileName);
     return userMapper.saveUser(user);
 } catch (IOException e) {
     throw new RuntimeException(e);
 }
}

注:这里使用file类的transferTo方法进行文件的输入,相较于原来使用的文件流的传入传出更加简便。

controller层:

@PostMapping("/register")
 public int addUser(User user, @RequestPart("icon")MultipartFile file){return userService.addUser(user,file);}

userMapper.xml:

<insert id="saveUser" >
     INSERT INTO user(username, password, name, head_icon)
     VALUES(#{user.username}, #{user.password}, #{user.name}, #{user.headIcon})
 </insert>

文件下载

配置如上

测试:

controller层:

//ResponseEntity表示响应的实体,这个实体只是包含响应的数据、状态码、状态信息、响应头等
@GetMapping("/icon/{filename}")
public ResponseEntity<byte[]> download(@PathVariable("filename")String fileName){
 return userService.download(fileName);
}

service层:响应头需要添加两次参数,一次为响应文件格式,一次为响应文件的下载形式和名字

ResponseEntity<byte[]> download(String fileName);
//-------------------------------------------------------------
@Override
public ResponseEntity<byte[]> download(String fileName) {
 File file = new File(SAVE_DIR,fileName);

 try {
     //将文件保存至一个字节数组中
     byte[] fileData = FileCopyUtils.copyToByteArray(file);
     //构建HTTP响应头
     HttpHeaders headers = new HttpHeaders();
     //响应数据的类型是可执行文件
     headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
     //设置响应文件的形式是以附件的形式存在,附件的名字如果是中文,那么必须要进行转码处理。
     //因为浏览器默认的编码格式是ISO-8859-1,而中文支持的格式是UTF-8,两种编码格式不一致,因此乱码
     //将中文字符串在当前编码下转换为字节数据,因为字节数据不存在乱码情况
     byte[] data = fileName.getBytes(StandardCharsets.UTF_8);
     //再将没有乱码的字节数据重新制定编码格式进行转换
     String s = new String(data, StandardCharsets.ISO_8859_1);
     headers.add(HttpHeaders.CONTENT_DISPOSITION,"attachment;filename="+s);
     return new ResponseEntity<>(fileData,headers, HttpStatus.OK);
 } catch (IOException e) {
     throw new RuntimeException(e);
 }
}

由于没有操作数据库,所以不写mapper层以及mapper映射文件

注:这里要把权限打开或者拦截器配置放行。

<!--放行-->
<mvc:interceptor>
 <mvc:mapping path="/**"/>
 <mvc:exclude-mapping path="/user/login"/>
 <mvc:exclude-mapping path="/user/icon/**"/>
 <bean class="com.qf.interceptor.PermissionInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>

===========================================================================

定时任务

在spring-mvc中配置:

<!--开启定时任务的注解驱动-->
<task:annotation-driven />

<!--控制器组件扫描-->
<context:component-scan base-package="com.qf.controller,com.qf.task" />

注:在扫描中需要扫描这个定时任务包

/**
 * 定时任务需要在配置文件中配置才会生效
*/
@Component
public class ClearTask {

 /**
     * cron表达式就是用来指定定时任务执行的时间
     * 涉及的单位顺序:秒 分 时 日 月 周 年
     *  * 表示所有值. 例如:在分的字段上设置 "*",表示每一分钟都会触发。
     *  ? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。
     *  - 表示区间。例如 在小时上设置 "10-12",表示 10,11,12点都会触发。
     *  , 表示指定多个值,例如在周字段上设置 "MON,WED,FRI" 表示周一,周三和周五触发
     *  / 用于递增触发。如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)
  */
 @Scheduled(cron = "0/5 48 11,12 * * ?")
 public void work(){
     System.out.println("定时任务执行");
 }
}

定时任务可能存在的问题:

Spring 默认配置下,将会使用具有单线程的 ScheduledExecutorService,单线程执行定时任务,如果某一个定时任务执行时间较长,将会影响其他定时任务执行。如果存在多个定时任务,为了保证定时任务执行时间的准确性,可以修改默认配置,使其使用多线程执行定时任务。

<!--开启定时任务的注解驱动-->
<task:annotation-driven />

<bean class="java.util.concurrent.ScheduledThreadPoolExecutor">
 <constructor-arg index="0" value="5"/><!--最多有5个线程同时执行任务-->
</bean>

<!--控制器组件扫描-->
<context:component-scan base-package="com.qfedu.ssm.controller,com.qfedu.ssm.task" />

===========================================================================

Excel处理

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便 。

Excel生成

即将数据库中的数据导出到Excel中

Excel生成的模板类:子类重写读取数据的方法,创建表,将数据写入表,使用的时候利用输出流。

/**
 * Excel导出工具模板
 */
public abstract class ExcelExportHandler<T> {

    private final int sheetDataCount;//一张表中需要存放多少条数据

    private final String sheetName;//表名

    private final Class<T> clazz;//表数据对应的类

    public ExcelExportHandler(int sheetDataCount, String sheetName, Class<T> clazz) {
        this.sheetDataCount = sheetDataCount;
        this.sheetName = sheetName;
        this.clazz = clazz;
    }

    /**
     * 获取数据总条数
     * @param params 查询条件
     * @return
     */
    abstract protected int getTotal(Map<String,Object> params);

    /**
     * 获取表中的数据
     * @param sheetNo 表的序号
     * @param sheetDataCount 表数据条数
     * @param params 查询条件
     * @return
     */
    abstract protected List<T> getSheetData(int sheetNo,int sheetDataCount,Map<String,Object> params);


    /**
     * 将数据写入excel中
     * @param out 写入的excel文件的输出流
     * @param params 查询条件
     */
    public void export(OutputStream out,Map<String,Object> params){
        try(ExcelWriter excelWriter = EasyExcel.write(out, clazz).build()) {
            int total = getTotal(params);
            int sheetCount = total / sheetDataCount;
            if(total % sheetDataCount>0)
                sheetCount++;
            for (int i = 1; i <= sheetCount; i++) {
                WriteSheet sheet = new WriteSheet();
                sheet.setSheetNo(i);
                sheet.setSheetName(sheetName);
                List<T> sheetData = getSheetData(i, sheetDataCount, params);
                excelWriter.write(sheetData,sheet);
            }
            excelWriter.finish();
        }
    }

    /**
     * 用于导出的方法
     * @param path 导出文件路径
     * @param params 查询条件
     */
    public void export(String path,Map<String,Object> params){
        File file = new File(path);
        File parentFile = file.getParentFile();
        if(!parentFile.exists()){
            parentFile.mkdirs();
        }
        try {
            FileOutputStream out = new FileOutputStream(file);
            export(out,params);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }


}

Excel解析

即读取Excel数据并导入数据库中

Excel解析的模版类:实现ReadListener接口创建监听器,重写invoke方法,此方法用于循环获取每一批次的数据,如果用于装数据的readDataList满了就进行导入,导入的方法由子类重写。

注:1.外部类有泛型了内部类上不用再次声明泛型。

2.重新赋值而不是清理,因为save方法里面可能保存到线程中处理,如果清理则数据丢失。

3.导入的数据量并非是批处理的整数倍,所以添加一个processRestData方法进行保存。

4.save方法耗时较大,会影响性能,因此用线程执行此方法。线程池是静态的,因为抽象类的子类不能一直创建线程池,内存会崩,因此在导入的方法执行的时候需要通过静态的任务类来执行。

package com.qf.excel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public abstract class ExcelImportHandler<T> {
    private final int batchCount;//批量处理的数据量
    private final Class<T> clazz;

    private static final ThreadPoolExecutor TASK_EXECUTOR = new ThreadPoolExecutor(5,10,1, TimeUnit.MINUTES,new LinkedBlockingDeque<>(2048));


    public ExcelImportHandler(int batchCount, Class<T> clazz) {
        this.batchCount = batchCount;
        this.clazz = clazz;
    }

    /**
     * 将数据保存到数据库中
     * @param dataList 读取的数据
     */
    protected abstract void save(List<T> dataList);

    /**
     * 创建监听器类,用于设定批量导入的时机
     */
    class ExcelDataReadListener implements ReadListener<T>{

        private List<T> readDataList = new ArrayList<>();
        
        @Override
        public void invoke(T data, AnalysisContext analysisContext) {
            readDataList.add(data);
            if(readDataList.size()==batchCount){
//                TASK_EXECUTOR.submit(()->{
//                    save(readDataList);
//                });
                TASK_EXECUTOR.submit(new ImportTask<>(readDataList,ExcelImportHandler.this));
                readDataList = new ArrayList<>();
            }

        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {

        }

        /**
         * 如果readDataList中存在没有写满的批次数据,就将剩下的数据进行导入
         */
        public void processRestData(){
            if(!readDataList.isEmpty()){
                TASK_EXECUTOR.submit(new ImportTask<>(readDataList,ExcelImportHandler.this));
            }
        }
    }

    static class ImportTask<E> implements Runnable{

        private final List<E> dataList;
        private final ExcelImportHandler<E> handler;

        public ImportTask(List<E> dataList, ExcelImportHandler<E> handler) {
            this.dataList = dataList;
            this.handler = handler;
        }

        @Override
        public void run() {
            handler.save(dataList);
        }
    }

    /**
     * 用于导入的方法
     * @param in 要读取的文件的输入流
     */
    public void importExcel(InputStream in){
        ExcelDataReadListener listener = new ExcelDataReadListener();
        EasyExcel.read(in,clazz,listener).doReadAll();
        listener.processRestData();
    }
    public void importExcel(String path){
        try {
            importExcel(new FileInputStream(path));
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

整合到SSM项目中

将ExcelExportHandler和ExcelImportHandler移入项目中,需要引入easyExcel依赖。

导入依赖:

<dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>easyexcel</artifactId>
 <version>3.3.2</version>
</dependency>

测试导入(Excel解析)

思路:前端上传文件,服务器读取文件数据将文件数据插入到数据库中

mapper层:

int batchSave(@Param("users") List<User> users);

Mapper.xml:

<insert id="batchSave">
    insert into user (username,password,name,head_icon) values
    <foreach collection="users" item="user" separator=",">
        (#{user.username},#{user.password},#{user.name},#{user.headIcon})
    </foreach>
</insert>

service层:(业务实现)

int batchSaveUser(List<User> users);
//-----------------------------------------------------------------
@Transactional(rollbackFor = Exception.class,isolation = Isolation.REPEATABLE_READ)
@Override
public int batchSaveUser(List<User> users) {
    return userMapper.batchSave(users);
}

子类模板继承(利用父类的构造方法填入参数):

public class UserImportHandler extends ExcelImportHandler{
    private UserService userService;

    public UserImportHandler(UserService userService) {
        super(100, User.class);
        this.userService = userService;
    }

    @Override
    protected void save(List dataList) {
        System.out.println("当前数据保存量"+dataList.size());
        userService.batchSaveUser(dataList);

    }
}

controller层:

@PostMapping("/import")
public String importUser(@RequestPart("user")MultipartFile file){
    UserImportHandler handler = new UserImportHandler(userService);
    try {
        handler.importExcel(file.getInputStream());
        return "文件导入成功";
    } catch (IOException e) {
        return "导入失败";
    }
}

测试导出(Excel生成)

思路:根据查询条件查询数据,将数据导出到Excel文件,把文件以字节流数组形式存放到响应头中供前端用户下载

UserDto:

@Data
public class UserDto {
    private String username;
    private String name;
}

mapper层:

int getTotal(@Param("params")Map<String,Object> params);

List<User> getUserData(@Param("params") Map<String,Object> params);

Mapper.xml:

<select id="getTotal" resultType="int">
    select count(0) from user
    <where>
        <if test="params.username!=null and params.username!=''">
            AND username = #{params.username}
        </if>
        <if test="params.name!=null and params.name!=''">
            AND name LIKE CONCAT('%',#{params.name},'%')
        </if>
    </where>
</select>

<select id="getUserData" resultType="user">
    select * from user
    <where>
        <if test="params.username!=null and params.username!=''">
            AND username = #{params.username}
        </if>
        <if test="params.name!=null and params.name!= ''">
            AND name LIKE CONCAT('%' ,#{params.name} ,'%')
        </if>
    </where>
    LIMIT #{params.offset},#{params.pageSize}
</select>

service层:(业务实现)

int getTotal(Map<String, Object> params);

List<User> getUserData(int sheetNo, int sheetDataCount, Map<String, Object> params);
//----------------------------------------------------------------------------------
@Override
public int getTotal(Map<String, Object> params) {
    return userMapper.getTotal(params);
}

@Override
public List<User> getUserData(int sheetNo, int sheetDataCount, Map<String, Object> params) {
    int offSet = sheetDataCount * (sheetNo - 1);
    params.put("offset",offSet);
    params.put("pageSize",sheetDataCount);
    return userMapper.getUserData(params);
}

子类模板继承:

public class UserExportHandler extends ExcelExportHandler<User>{

    private UserService userService;
    public UserExportHandler(UserService userService) {
        super(100,"用户信息表", User.class);
        this.userService = userService;
    }

    @Override
    protected int getTotal(Map<String,Object> params) {
        return userService.getTotal(params);
    }

    @Override
    protected List<User> getSheetData(int sheetNo, int sheetDataCount, Map<String,Object> params) {
        return userService.getUserData(sheetNo,sheetDataCount,params);

    }
}

controller层:

通过工具模板的子类获取子类对象,调用其handler方法实现导出,将数据导出到输出流中,这里需要把userDto转换为jsonObject对象(等同于Map)。将输入流转换为字节流数组,即Excel文件数据,然后将数据,响应头放到ResponseEntity中供用户下载。

@GetMapping("/export")
public ResponseEntity<byte[]> exportUser(UserDto userDto){
    UserExportHandler handler = new UserExportHandler(userService);
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    JSONObject json = (JSONObject) JSONObject.toJSON(userDto);
    handler.export(out,json);
    byte[] data = out.toByteArray();
    HttpHeaders headers = new HttpHeaders();
    headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
    String fileName = "用户信息表.xlsx";
    byte[] bytes = fileName.getBytes(StandardCharsets.UTF_8);
    fileName = new String(bytes, StandardCharsets.ISO_8859_1);
    headers.add(HttpHeaders.CONTENT_DISPOSITION,"attachment;filename=" + fileName);
    return new ResponseEntity<>(data,headers, HttpStatus.OK);
}

===========================================================================

Spring事务传播行为

REQUIRED:支持当前事务,如果不存在事务就自己创建一个事务。

SUPPORTS:支持当前事务,如果不存在事务自己也不创建事务,以非事务的形式执行。

MANDATORY:支持当前事务,且要求必须有事务,否则不执行并抛出异常。

REQUIRES_NEW:当前有事务的话就挂起来,开启新的事物执行完自身后再恢复之前的事务,如果没有事务就直接创建一个新的事物执行自身。

NOT_SUPPORTED:不支持当前事务,如果当前有事务则挂起,以非事务的形式执行自身之后再恢复事务。

NEVER:不支持事务的形式执行,如果有事务就抛异常。

NESTED:嵌套,如果没有事务就自己创建新的事务来执行,如果有则在当前的事务中创建一个子事务嵌套执行。嵌套的子事务有独立的回滚点,可以自己进行回滚,但是如果父事务回滚那么子事务也会相应回滚。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1923503.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【机器学习】和【人工智能】在航空航天中的应用

作者主页: 知孤云出岫 目录 引言机器学习和人工智能在航空航天中的应用1. 预测性维护2. 飞行路径优化3. 自动驾驶飞行器 未来展望1. 增强人机协作2. 更智能的空中交通管理3. 高效的航空制造 结论参考文献 引言 随着科技的迅猛发展&#xff0c;机器学习和人工智能&#xff08;…

Goland 通道

channel通道 目录 channel通道 channel介绍 channel基本使用 有缓存通道和无缓存通道的区别 通道的初始化&#xff0c;写入数据到通道&#xff0c;从通道读取数据及基本的注意事项 channel的关闭和遍历 channel的关闭 为什么关闭 如何优雅地关闭通道 channel的遍历 chan…

亚马逊插件安装教程,新手必学的两个选品步骤

亚马逊插件安装教程&#xff0c;跟卖新手必学&#xff0c;选品两个步骤。 大家好&#xff0c;今天讲下erp的插件下载和跟卖采集。erp更新到13.8版本了。 点击右上角的插件下载&#xff0c;已经下载到桌面了&#xff0c;已被压缩。 点击设置&#xff0c;选择扩展&#xff0c;…

python-亲和数(赛氪OJ)

[题目描述] 古希腊数学家毕达哥拉斯在自然数研究中发现&#xff0c;220 的所有真约数(即不是自身的约数)之和为&#xff1a; 1245101120224455110&#xff1d;284 。 而 284 的所有真约为 1 、 2 、 4 、 71 、 142 &#xff0c;加起来恰好为 220 。人们对这样的数感到很惊奇&a…

【研路导航】重庆大学计算机保研面试真题分享交流

写在前面 在保研的道路上&#xff0c;面试是非常重要的一环。这里是成功保研到重庆大学的学长的计算机保研面试的部分真题及详细解答 ! Q 快速排序和合并排序&#xff1a; 快速排序(quicksort) 和合并排序(merge sort) 两种方法都将输入的待排序序列划分为2个子序列&#xf…

centos单机配置多个内网IP地址

centos单机配置多个内网IP地址 引配置1. 查看当前网络IP配置2. 打开网络配置目录3. 设置静态IP4. 编辑ifcfg-eno1:15. 重启网络配置 引 同一个局域网&#xff0c;但是对接的多个子系统使用了不同的网段&#xff0c;如一个系统主机IP地址是192.168.10.1&#xff0c;另一个系统主…

2024 辽宁省大学数学建模竞赛A题 风切变影响及处置 完整思路 代码结果分享(仅供学习)

风切变对航空安全会构成危害。航空人员需了解相关知识&#xff0c;掌握相应技术&#xff0c;从而在遭遇上述天气时最大程度的规避风险&#xff0c;保证飞行安全。 风切变是指在大气中相对比较短的距离内或时间段内产生的风速大小、方向大幅度变化的现象。通常将发生在距离地面…

Rust Hello

首先还是安装&#xff1a; 一定要换源&#xff0c;否则真的太慢了。 curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh 就是~/.cargo/config [source.crates-io] # 替换成你偏好的镜像源 replace-with tuna# 清华大学 5mb [source.tuna] registry "htt…

课程的概述

课程概述 课程类型 课程理论流派 制约课程开发的因素 课程设计的概念及两种模式 课程内容 课程评价 新课程改革理念

python基础语法 005 函数1-2 函数作用域

1 函数续 1.7 函数作用域 1.7.1 全局变量 定义在函数外部的变量全局变量在函数内部和函数外部都可以访问使用 a 100 def run():print("a {}".format(a))print(a) print(run())1.7.2 局部变量 函数是一个黑盒子&#xff0c;外面看不到盒子里面的东西&#xff0…

QEMU模拟器源码编译与使用

QEMU模拟器源码编译与使用 1 编译MySBIBenOS2 编译QEMU3 QEMU运行MySBIBenOS4 使用gdb调试QEMU5 通过QEMU调试指令6 小结 本文属于 《RISC-V指令集差分测试&#xff08;DiffTest&#xff09;系列教程》之一&#xff0c;欢迎查看其它文章。 1 编译MySBIBenOS 参考《NEMU模拟器…

AI绘画Midijourney操作技巧及变现渠道喂饭式教程!

前言 盘点Midijourney&#xff08;AIGF&#xff09;热门赚米方法&#xff0c;总有一种适合你之AI绘画操作技巧及变现渠道剖析 【表情包制作】 首先我们对表情包制作进行详细的讲解&#xff1a; 当使用 Midjourney&#xff08;AIGF&#xff09; 绘画来制作表情包时&#xff…

【Linux网络】网络基础

本篇博客整理了 Linux 网络编程的前置知识&#xff0c;例如网络的发展、协议和协议栈分层、网络通信原理、网络地址等&#xff0c;为后续进入 Linux 网络编程作铺垫。 目录 一、网络发展 二、网络协议 1&#xff09;协议的作用 2&#xff09;协议栈 3&#xff09;协议分层…

自动驾驶(萝卜快跑)是毁灭出租司机工作机会的灾难?

引言 自动驾驶技术的飞速发展在全球范围内引发了广泛的讨论和担忧&#xff0c;特别是在中国&#xff0c;自动驾驶出租车服务“萝卜快跑”成为了热门话题。本文探讨自动驾驶对出租司机工作机会的影响&#xff0c;以及这种技术变革背后的社会经济因素。 自动驾驶的历史与现状 …

yoloV8导出engine模型

yoloV8导出engine模型 引言&#xff1a; 目的是TensorRT加速YOLO实例分割模型。 本博客记录达成此目的所需的步骤&#xff0c;及步骤中可能遇到的问题。 as follow&#xff1a; 1. 首先导出onnx模型的脚本命令&#xff1a; yolo export modelbest.pt formatonnx opset12 simpl…

全网都在找数学建模美赛模版,免费直接复制,无需关注公众号!!!

文件夹结构如下 美版论文模版预览(截选) tex文件代码如下 \documentclass{mcmthesis} \mcmsetup{CTeX false, % 使用 CTeX 套装时&#xff0c;设置为 truetcn 1111111, problem ABCDEF,sheet true, titleinsheet true, keywordsinsheet true,titlepage false, abstra…

C++ | Leetcode C++题解之第226题翻转二叉树

题目&#xff1a; 题解&#xff1a; class Solution { public:TreeNode* invertTree(TreeNode* root) {if (root nullptr) {return nullptr;}TreeNode* left invertTree(root->left);TreeNode* right invertTree(root->right);root->left right;root->right …

深度学习5 神经网络

生物神经网络是指人的大脑&#xff0c;这是人工神经网络的技术原型。根据生物神经网络的原理&#xff0c;人们用计算机复现了简化的神经网络。当然&#xff0c;人工神经网络是机器学习的一大分支。 1.基本组成 1.1神 经 元 神经元是神经网络的基本组成。激活函数又称作激励函…

【java报错已解决】“Array Out of Bounds“

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引言一、问题描述1.1 报错示例1.2 报错分析1.3 解决思路 二、解决方法&#xff1a;2.1 方法一&#xff1a;范围检查2.…

案例效果展示小程序制作承载信息宣传

如工商财税、房地产、装修、鲜花、五金等很多行业&#xff0c;都有商品/服务/案例/效果等展示宣传获客的需求&#xff0c;传统线下门店/传单方式效果比较低&#xff0c;线上渠道则更多更利于商家随时开展。 然而更多的图片和文字介绍分享混乱难以整理且无法全面覆盖&#xff0…