将HTML转换为Apache POI的RichTextString

1.概述

在本教程中,我们将构建一个将HTML作为输入的应用程序,并使用提供HTML的RichText表示形式创建Microsoft Excel工作簿。 为了生成Microsoft Excel工作簿,我们将使用Apache POI 。 为了分析HTML,我们将使用Jericho。

Github上提供了本教程的完整源代码。

2.什么是耶利哥?

Jericho是一个Java库 ,它允许对HTML文档的各个部分(包括服务器端标签)进行分析和操作,同时逐字再现任何无法识别或无效HTML。 它还提供了高级HTML表单操作功能。 它是一个开放源代码库,使用以下许可证发行: Eclipse公共许可证(EPL) , GNU通用公共许可证(LGPL)和Apache许可证 。

我发现Jericho非常易于使用,可以实现我将HTML转换为RichText的目标。

3. pom.xml

这是我们正在构建的应用程序所需的依赖项。 请注意,对于此应用程序,我们必须使用Java 9 。 这是因为我们使用的java.util.regex appendReplacement方法自Java 9起才可用。

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.9.RELEASE</version><relativePath /> <!-- lookup parent from repository -->
</parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>9</java.version>
</properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-batch</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.7</version></dependency><dependency><groupId>org.springframework.batch</groupId><artifactId>spring-batch-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.15</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.15</version></dependency><!-- https://mvnrepository.com/artifact/net.htmlparser.jericho/jericho-html --><dependency><groupId>net.htmlparser.jericho</groupId><artifactId>jericho-html</artifactId><version>3.4</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><!-- legacy html allow --><dependency><groupId>net.sourceforge.nekohtml</groupId><artifactId>nekohtml</artifactId></dependency>
</dependencies>

4.网页– Thymeleaf

我们使用Thymeleaf来创建一个基本页面,该页面具有带有文本区域的表单。 Github上提供了Thymeleaf页面的源代码。 如果愿意,可以使用RichText编辑器替换此textarea,例如CKEditor。 我们只需要注意使用适当的setData方法使AJAX的数据正确即可。 在Spring Boot中,以前有一个关于CKeditor的教程,标题为CKEditor,名为AJAX 。

5.控制器

在我们的控制器中,我们将自动装配JobLauncher和一个Spring Batch作业,我们将创建一个名为GenerateExcel的作业 。 自动装配这两个类使我们可以在POST请求发送到“ / export”时按需运行Spring Batch Job GenerateExcel

要注意的另一件事是,为确保Spring Batch作业将运行一次以上,我们在此代码中包含唯一参数: addLong(“ uniqueness”,System.nanoTime())。toJobParameters() 。 如果我们不包括唯一的参数,则可能会发生错误,因为只能创建和执行唯一的JobInstances,否则Spring Batch无法区分第一个JobInstance和第二个JobInstance

@Controller
public class WebController {private String currentContent;@AutowiredJobLauncher jobLauncher;@AutowiredGenerateExcel exceljob; @GetMapping("/")public ModelAndView getHome() {ModelAndView modelAndView = new ModelAndView("index");return modelAndView;}@PostMapping("/export")public String postTheFile(@RequestBody String body, RedirectAttributes redirectAttributes, Model model)throws IOException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException {setCurrentContent(body);Job job = exceljob.ExcelGenerator();jobLauncher.run(job, new JobParametersBuilder().addLong("uniqueness", System.nanoTime()).toJobParameters());return "redirect:/";}//standard getters and setters}

6.批处理作业

在批处理作业的步骤1中,我们调用getCurrentContent()方法来获取传递到Thymeleaf表单中的内容,创建一个新的XSSFWorkbook,指定一个任意的Microsoft Excel Sheet选项卡名称,然后将所有三个变量都传递到createWorksheet方法中我们将在本教程的下一步中进行以下操作:

@Configuration
@EnableBatchProcessing
@Lazy
public class GenerateExcel {List<String> docIds = new ArrayList<String>();@Autowiredprivate JobBuilderFactory jobBuilderFactory;@Autowiredprivate StepBuilderFactory stepBuilderFactory;@AutowiredWebController webcontroller;@AutowiredCreateWorksheet createexcel;@Beanpublic Step step1() {return stepBuilderFactory.get("step1").tasklet(new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception, JSONException {String content = webcontroller.getCurrentContent();System.out.println("content is ::" + content);Workbook wb = new XSSFWorkbook();String tabName = "some";createexcel.createWorkSheet(wb, content, tabName);return RepeatStatus.FINISHED;}}).build();}@Beanpublic Job ExcelGenerator() {return jobBuilderFactory.get("ExcelGenerator").start(step1()).build();}}

我们还在其他教程中介绍了Spring Batch,例如将XML转换为JSON + Spring Batch和Spring Batch CSV处理 。

7. Excel创建服务

我们使用各种类来创建我们的Microsoft Excel文件。 在将HTML转换为RichText时,顺序很重要,因此这将是重点。

7.1 RichTextDetails

一个带有两个参数的类:一个字符串,其内容将变为RichText,一个字体映射。

public class RichTextDetails {private String richText;private Map<Integer, Font> fontMap;//standard getters and setters@Overridepublic int hashCode() {// The goal is to have a more efficient hashcode than standard one.return richText.hashCode();}

7.2 RichTextInfo

一个POJO,它将跟踪RichText的位置以及其他内容:

public class RichTextInfo {private int startIndex;private int endIndex;private STYLES fontStyle;private String fontValue;// standard getters and setters, and the like

7.3样式

一个包含要处理HTML标记的枚举。 我们可以根据需要添加以下内容:

public enum STYLES {BOLD("b"), EM("em"), STRONG("strong"), COLOR("color"), UNDERLINE("u"), SPAN("span"), ITALLICS("i"), UNKNOWN("unknown"),PRE("pre");// standard getters and setters

7.4 TagInfo

POJO跟踪标签信息:

public class TagInfo {private String tagName;private String style;private int tagType;// standard getters and setters

7.5 HTML为RichText

这不是一个小类,所以让我们按方法进行分类。

本质上,我们使用div标签将任意HTML包围起来,因此我们知道我们在寻找什么。 然后,我们在div标签中查找所有元素,将每个元素添加到RichTextDetails的ArrayList中,然后将整个ArrayList传递给mergeTextDetails方法。 mergeTextDetails返回RichtextString,这是我们需要设置单元格值的内容:

public RichTextString fromHtmlToCellValue(String html, Workbook workBook){Config.IsHTMLEmptyElementTagRecognised = true;Matcher m = HEAVY_REGEX.matcher(html);String replacedhtml =  m.replaceAll("");StringBuilder sb = new StringBuilder();sb.insert(0, "<div>");sb.append(replacedhtml);sb.append("</div>");String newhtml = sb.toString();Source source = new Source(newhtml);List<RichTextDetails> cellValues = new ArrayList<RichTextDetails>();for(Element el : source.getAllElements("div")){cellValues.add(createCellValue(el.toString(), workBook));}RichTextString cellValue = mergeTextDetails(cellValues);return cellValue;}

如上所述,我们在此方法中传递了RichTextDetails的ArrayList。 Jericho的设置采用布尔值来识别空标签元素,例如
:已识别Config.IsHTMLEmptyElementTag。 与在线富文本编辑器打交道时,这可能很重要,因此我们将其设置为true。 因为我们需要跟踪元素的顺序,所以我们使用LinkedHashMap而不是HashMap。

private static RichTextString mergeTextDetails(List<RichTextDetails> cellValues) {Config.IsHTMLEmptyElementTagRecognised = true;StringBuilder textBuffer = new StringBuilder();Map<Integer, Font> mergedMap = new LinkedHashMap<Integer, Font>(550, .95f);int currentIndex = 0;for (RichTextDetails richTextDetail : cellValues) {//textBuffer.append(BULLET_CHARACTER + " ");currentIndex = textBuffer.length();for (Entry<Integer, Font> entry : richTextDetail.getFontMap().entrySet()) {mergedMap.put(entry.getKey() + currentIndex, entry.getValue());}textBuffer.append(richTextDetail.getRichText()).append(NEW_LINE);}RichTextString richText = new XSSFRichTextString(textBuffer.toString());for (int i = 0; i < textBuffer.length(); i++) {Font currentFont = mergedMap.get(i);if (currentFont != null) {richText.applyFont(i, i + 1, currentFont);}}return richText;}

如上所述,我们使用Java 9来将StringBuilder与java.util.regex.Matcher.appendReplacement结合使用 。 为什么? 那是因为StringBuffer的运行速度比StringBuilder慢。 StringBuffer函数是为了线程安全而同步的,因此速度较慢。

我们使用Deque而不是Stack,因为Deque接口提供了更完整和一致的LIFO堆栈操作集:

static RichTextDetails createCellValue(String html, Workbook workBook) {Config.IsHTMLEmptyElementTagRecognised  = true;Source source = new Source(html);Map<String, TagInfo> tagMap = new LinkedHashMap<String, TagInfo>(550, .95f);for (Element e : source.getChildElements()) {getInfo(e, tagMap);}StringBuilder sbPatt = new StringBuilder();sbPatt.append("(").append(StringUtils.join(tagMap.keySet(), "|")).append(")");String patternString = sbPatt.toString();Pattern pattern = Pattern.compile(patternString);Matcher matcher = pattern.matcher(html);StringBuilder textBuffer = new StringBuilder();List<RichTextInfo> textInfos = new ArrayList<RichTextInfo>();ArrayDeque<RichTextInfo> richTextBuffer = new ArrayDeque<RichTextInfo>();while (matcher.find()) {matcher.appendReplacement(textBuffer, "");TagInfo currentTag = tagMap.get(matcher.group(1));if (START_TAG == currentTag.getTagType()) {richTextBuffer.push(getRichTextInfo(currentTag, textBuffer.length(), workBook));} else {if (!richTextBuffer.isEmpty()) {RichTextInfo info = richTextBuffer.pop();if (info != null) {info.setEndIndex(textBuffer.length());textInfos.add(info);}}}}matcher.appendTail(textBuffer);Map<Integer, Font> fontMap = buildFontMap(textInfos, workBook);return new RichTextDetails(textBuffer.toString(), fontMap);}

我们可以在这里看到RichTextInfo的使用位置:

private static Map<Integer, Font> buildFontMap(List<RichTextInfo> textInfos, Workbook workBook) {Map<Integer, Font> fontMap = new LinkedHashMap<Integer, Font>(550, .95f);for (RichTextInfo richTextInfo : textInfos) {if (richTextInfo.isValid()) {for (int i = richTextInfo.getStartIndex(); i < richTextInfo.getEndIndex(); i++) {fontMap.put(i, mergeFont(fontMap.get(i), richTextInfo.getFontStyle(), richTextInfo.getFontValue(), workBook));}}}return fontMap;}

我们在哪里使用STYLES枚举:

private static Font mergeFont(Font font, STYLES fontStyle, String fontValue, Workbook workBook) {if (font == null) {font = workBook.createFont();}switch (fontStyle) {case BOLD:case EM:case STRONG:font.setBoldweight(Font.BOLDWEIGHT_BOLD);break;case UNDERLINE:font.setUnderline(Font.U_SINGLE);break;case ITALLICS:font.setItalic(true);break;case PRE:font.setFontName("Courier New");case COLOR:if (!isEmpty(fontValue)) {font.setColor(IndexedColors.BLACK.getIndex());}break;default:break;}return font;}

我们正在使用TagInfo类来跟踪当前标签:

private static RichTextInfo getRichTextInfo(TagInfo currentTag, int startIndex, Workbook workBook) {RichTextInfo info = null;switch (STYLES.fromValue(currentTag.getTagName())) {case SPAN:if (!isEmpty(currentTag.getStyle())) {for (String style : currentTag.getStyle().split(";")) {String[] styleDetails = style.split(":");if (styleDetails != null && styleDetails.length > 1) {if ("COLOR".equalsIgnoreCase(styleDetails[0].trim())) {info = new RichTextInfo(startIndex, -1, STYLES.COLOR, styleDetails[1]);}}}}break;default:info = new RichTextInfo(startIndex, -1, STYLES.fromValue(currentTag.getTagName()));break;}return info;}

我们处理HTML标签:

private static void getInfo(Element e, Map<String, TagInfo> tagMap) {tagMap.put(e.getStartTag().toString(),new TagInfo(e.getStartTag().getName(), e.getAttributeValue("style"), START_TAG));if (e.getChildElements().size() > 0) {List<Element> children = e.getChildElements();for (Element child : children) {getInfo(child, tagMap);}}if (e.getEndTag() != null) {tagMap.put(e.getEndTag().toString(),new TagInfo(e.getEndTag().getName(), END_TAG));} else {// Handling self closing tagstagMap.put(e.getStartTag().toString(),new TagInfo(e.getStartTag().getName(), END_TAG));}}

7.6创建工作表

使用StringBuilder,我创建了一个要写入FileOutPutStream的字符串。 在实际应用中,应由用户定义。 我在两个不同的行上附加了文件夹路径和文件名。 请将文件路径更改为您自己的文件路径。

sheet.createRow(0)在第一行创建一行,而dataRow.createCell(0)在该行的列A中创建一个单元格。

public void createWorkSheet(Workbook wb, String content, String tabName) {StringBuilder sbFileName = new StringBuilder();sbFileName.append("/Users/mike/javaSTS/michaelcgood-apache-poi-richtext/");sbFileName.append("myfile.xlsx");String fileMacTest = sbFileName.toString();try {this.fileOut = new FileOutputStream(fileMacTest);} catch (FileNotFoundException ex) {Logger.getLogger(CreateWorksheet.class.getName()).log(Level.SEVERE, null, ex);}Sheet sheet = wb.createSheet(tabName); // Create new sheet w/ Tab namesheet.setZoom(85); // Set sheet zoom: 85%// content rich textRichTextString contentRich = null;if (content != null) {contentRich = htmlToExcel.fromHtmlToCellValue(content, wb);}// begin insertion of values into cellsRow dataRow = sheet.createRow(0);Cell A = dataRow.createCell(0); // Row NumberA.setCellValue(contentRich);sheet.autoSizeColumn(0);try {/// Write the output to a filewb.write(fileOut);fileOut.close();} catch (IOException ex) {Logger.getLogger(CreateWorksheet.class.getName()).log(Level.SEVERE, null, ex);}}

8.演示

我们访问localhost:8080

我们用一些HTML输入一些文本:

我们打开excel文件,然后看到我们创建的RichText:

9.结论

我们可以看到将HTML转换为Apache POI的RichTextString类并不是一件容易的事。 但是,对于商业应用程序而言,将HTML转换为RichTextString至关重要,因为在Microsoft Excel文件中可读性很重要。 我们构建的应用程序的性能可能还有改进的空间,但是我们涵盖了构建此类应用程序的基础。

完整的源代码可在Github上找到。

翻译自: https://www.javacodegeeks.com/2018/01/converting-html-richtextstring-apache-poi.html

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

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

相关文章

MySQL常见问题的解决,root用户密码忘记,不是内部或外部命令,修改数据库和表的字符编码,命令行客户端的字符集问题

文章目录问题1&#xff1a;root用户密码忘记&#xff0c;重置的操作问题2&#xff1a;mysql命令报“不是内部或外部命令”问题3&#xff1a;错误ERROR &#xff1a;没有选择数据库就操作表格和数据问题4&#xff1a;命令行客户端的字符集问题问题5&#xff1a;修改数据库和表的…

win10下最新MySQL8.0安装与环境配置,Navicat图形化管理工具连接,完整详细可收藏

微信公众号&#xff1a;创享日记 发送&#xff1a;sqlyog 获取SQLyog客户端安装包 文章目录一、MySQL的卸载二、MySQL的下载安装和配置三、MySQL的开启和登录四、MySQL图形化管理工具一、MySQL的卸载 步骤1&#xff1a;停止MySQL服务 在卸载之前&#xff0c;先停止MySQL8.0的服…

Fn函数来构建Oracle ADF应用程序

在我以前的一篇文章中&#xff0c;我描述了如何创建一个Docker容器作为ADF应用程序的构建器。 在这里&#xff0c;我将展示如何将此容器用作 在FN平台的功能 。 首先&#xff0c;让我们更新容器&#xff0c;使其符合功能要求&#xff0c;这意味着可以将其作为接受某些参数的可…

MySQL基本的SELECT语句,SQL语言概述,基础和重要不冲突,完整详细可收藏

文章目录1、SQL语言的分类2、SQL语言的规则与规范3、基本的SELECT语句4、DESCRIBE显示表结构5、WHERE过滤数据1、SQL语言的分类 SQL语言在功能上主要分为如下3大类&#xff1a; DDL&#xff08;Data Definition Languages、数据定义语言&#xff09;&#xff0c;这些语句定义…

MySQL运算符,SQL,算术比较逻辑位,优先级,正则表达式,完整详细可收藏

文章目录1、算术运算符2、比较运算符3、逻辑运算符4、位运算符5、运算符的优先级拓展&#xff1a;使用正则表达式查询1、算术运算符 算术运算符主要用于数学运算&#xff0c;其可以连接运算符前后的两个数值或表达式&#xff0c;对数值或表达式进行加&#xff08;&#xff09;…

苹果原装转接头不能连html,变革之际 iPhone 7P原装lightning耳机及转接头拆解

苹果最新推出的iphone7 系列&#xff0c;最大的改动是取消了3.5mm耳机孔&#xff0c;用lightning接口取代&#xff0c;这个看来也是趋势&#xff0c;安卓手机有几款也是直接用Type-c的接口解决音频、充电问题的。lightning耳机的接口最大的不便不能同时听歌充电&#xff0c;只能…

java 反射api_Java的反射API

java 反射api如果您曾经问​​过自己以下问题&#xff1a; –“如何在字符串中仅包含其名称的方法调用&#xff1f;” –“如何动态列出类中的所有属性&#xff1f;” –“如何编写将任何给定对象的状态重置为默认值的方法&#xff1f;” 然后&#xff0c;您可能已经听说过…

MySQL多表查询,SQL,笛卡尔积等值连接自连接外连接,SQL99新特性,完整详细可收藏

文章目录1、笛卡尔积&#xff08;交叉连接&#xff09;2、等值连接 vs 非等值连接3、自连接 vs 非自连接4、内连接 vs 外连接5、SQL99语法新特性多表查询&#xff0c;也称为关联查询&#xff0c;指两个或更多个表一起完成查询操作。 前提条件&#xff1a;这些一起查询的表之间…

针对开发人员的Microsoft SQL Server元数据

Microsoft SQL Server 2016最近在关系数据库管理系统&#xff08;RDBMS&#xff09;中处于领先地位。 高性能&#xff0c;安全性&#xff0c;分析和云兼容性的结合使其成为领先的RDBMS 。 SQL Server 2017甚至支持R和Python编程语言&#xff0c;这进一步提高了它在学术机构中的…

计算机怎么录制视频教程,怎么录制视频教程?查看电脑具体录屏方法

怎么录制视频教程&#xff1f;查看电脑具体录屏方法2020年04月07日 15:25作者&#xff1a;黄页编辑&#xff1a;黄页分享怎么录制视频教程?有时候在生活中遇到了喜欢的视频、直播等&#xff0c;想在电脑上通过录屏的方式记录下来。高质量的录屏视频怎么实现?其实具体录屏的方…

加密解密,MySQL单行函数,数学函数字符串日期时间,流程控制,完整详细可收藏查询SQL

前些天发现了十分不错的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;没有广告&#xff0c;分享给大家&#xff0c;大家可以自行看看。&#xff08;点击跳转人工智能学习资料&#xff09; 文章目录1、函数的理解2、数值函数2.1 基本函数2.2 角度与弧度…

无锡太湖学院计算机科学与技术宿舍,无锡太湖学院宿舍条件,宿舍环境图片(10篇)...

无锡太湖学院宿舍条件,宿舍环境图片(10篇)大学宿舍是一个神奇的地方&#xff0c;来自于天南地北的同学聚集在一个屋檐下&#xff0c;并将在一起度过宝贵的大学时光。人们常说&#xff0c;没有住过宿舍的大学是不完整的。当然不同的大学宿舍环境条件都不一样。高考升学网小编本文…

SELECT执行过程,MySQL聚合函数,多行分组函数,GROUP BY HAVING,详细完整可收藏

文章目录1、聚合函数介绍2、五个常用聚合函数3、GROUP BY4、HAVING5、SELECT的执行过程1、聚合函数介绍 聚合函数作用于一组数据&#xff0c;并对一组数据返回一个值。聚合函数不能嵌套调用。比如不能出现类似“AVG(SUM(字段名称))”形式的调用。 2、五个常用聚合函数 2.1 A…

嵌套套娃,MySQL子查询,单行与多行子查询,相关和不相关(关联)子查询,完整详细可收藏

文章目录1、需求分析与问题解决2、单行子查询3、多行子查询4、相关子查询5、抛一个思考题子查询指一个查询语句嵌套在另一个查询语句内部的查询&#xff0c;这个特性从MySQL 4.1开始引入。SQL 中子查询的使用大大增强了 SELECT 查询的能力&#xff0c;因为很多时候查询需要从结…

键盘连接在计算机的,技巧:如何在计算机键盘上输入连字符和破折号?

文字/连字符在哪里&#xff0c;我该如何输入&#xff1f;例如&#xff0c;某些英语单词中有连字符&#xff0c;例如高层。当然&#xff0c;有时是否有连字符都没有关系&#xff0c;并且不影响其含义。但是&#xff0c;有时有必要。例如&#xff0c;单词在行尾的位置是不够的。您…

删库跑路?不可回滚?MySQL创建和管理表,修改清空表,MySQL8新特性DDL原子化,完整详细可收藏

文章目录1、基础知识2、创建和管理数据库3、创建表4、修改表5、重命名表6、 删除表7、清空表8、MySQL8新特性—DDL的原子化1、基础知识 1.1 一条数据存储的过程 存储数据是处理数据的第一步 。只有正确地把数据存储起来&#xff0c;我们才能进行有效的处理和分析。否则&#x…

不全?MySQL数据类型精讲,定点日期枚举文本字符串,json二进制,空间,选择建议,完整详细可收藏

文章目录1. MySQL中的数据类型2. 整数类型3. 浮点数类型4. 定点数类型5. 位类型&#xff1a;BIT6. 日期与时间类型7. 文本字符串类型8. ENUM类型9. SET类型10. 二进制字符串类型11. JSON 类型12. 空间类型13. 选择建议1. MySQL中的数据类型 常见数据类型的属性&#xff0c;如下…

约束,MySQL约束,非空默认值,主键外键唯一自增,完整详细可收藏

文章目录1. 约束(constraint)概述2. 非空约束3. 唯一性约束4. PRIMARY KEY 约束5. 自增列&#xff1a;AUTO_INCREMENT6. FOREIGN KEY 约束7. CHECK 约束8. DEFAULT约束9. 面试1. 约束(constraint)概述 1.1 为什么需要约束 数据完整性&#xff08;Data Integrity&#xff09;是…

如何测试Java类的线程安全性

我在最近的一次网络研讨会中谈到了这个问题&#xff0c;现在是时候以书面形式进行解释了。 线程安全是Java等语言/平台中类的重要品质&#xff0c;我们经常在线程之间共享对象。 缺乏线程安全性导致的问题很难调试&#xff0c;因为它们是零星的并且几乎不可能有意复制。 您如何…

争议?MySQL存储过程与函数,封装,体,完整详细可收藏

文章目录1. 存储过程概述2. 创建存储过程3. 调用存储过程4. 存储函数的使用5. 存储过程和函数的查看、修改、删除6. 关于存储过程使用的争议MySQL从5.0版本开始支持存储过程和函数。存储过程和函数能够将复杂的SQL逻辑封装在一起&#xff0c;应用程序无须关注存储过程和函数内部…