介绍
开发环境:
- Win10
- Java 8
- maven 3.6.3
- VS-Code 1.56.2
项目结构:
Spring 5.3.6,Hibernate 5.4.30.Final,tomcat 8.5
项目最终结构:
为了尽量能看到全貌,没有子文件夹的文件夹都折叠了,pom.xml 是在src的同级目录下。
完整的结构如下:
├───.classpath
├───pom.xml
├───.project
├───.settings
├───.vscode
├───src
│ ├───main
│ │ ├───filters
│ │ │ ├───dev
│ │ │ └───prod
│ │ ├───java
│ │ │ └───com
│ │ │ └───seagate
│ │ │ └───flawscananalytic
│ │ │ ├───bean
│ │ │ ├───controller
│ │ │ ├───dao
│ │ │ │ └───impl
│ │ │ ├───entity
│ │ │ ├───model
│ │ │ ├───repository
│ │ │ ├───service
│ │ │ │ └───impl
│ │ │ └───util
│ │ ├───resources
│ │ │ ├───db
│ │ │ │ ├───mysql
│ │ │ │ └───pgsql
│ │ │ └───META-INF
│ │ └───webapp
│ │ ├───data
│ │ │ └───images
│ │ ├───logs
│ │ ├───views
│ │ │ ├───common
│ │ │ ├───issue
│ │ │ └───releaseNote
│ │ └───WEB-INF
│ └───test
│ ├───java
│ │ └───com
│ │ └───seagate
│ │ └───flawscananalytic
│ │ └───test
│ └───resources
│ └───META-INF
静态资源文件太多,从树里面删掉了,位于src/main/webapp/resources。target也删掉了。
gitee:ssh_demo
过程
VS-Code Java环境
VS-Code Java环境的搭建,网上有很多教程,应该都能搭建起来。主要是安装Java Extension Package插件,然后配置好Java和maven环境。
2.1 创建Maven项目
在vscode中使用命令 maven create maven project,选择maven-archetype-webapp,然后填好group ID和 artifact ID。创建完项目后,用VS-Code 打开项目。然后手动添加缺失的几个目录,结果如下:
.
|-- pom.xml
|-- src
| |-- main
| | |-- java
| | | `-- com
| | | `-- example
| | | |-- bean
| | | |-- controller
| | | |-- dao
| | | | `-- impl
| | | |-- entity
| | | |-- model
| | | |-- respository
| | | |-- service
| | | | `-- impl
| | | `-- util
| | |-- resources
| | `-- webapp
| | |-- WEB-INF
| | | `-- web.xml
| | `-- index.jsp
| `-- test
| |-- java
| | `-- com
| | `-- example
| `-- resources
`-- target
|-- classes
| `-- com
| `-- example
| |-- bean
| |-- controller
| |-- dao
| | `-- impl
| |-- entity
| |-- model
| |-- respository
| |-- service
| | `-- impl
| `-- util
|-- generated-sources
| `-- annotations
|-- generated-test-sources
| `-- test-annotations
`-- test-classes
`-- com
`-- example
2.2 环境配置
有三个环境,jetty的开发环境,tomcat的开发环境和发布环境。开发环境是用MySQL8,发布环境是用Postgresql。
在src/main 下新建文件夹src/main/filters/dev 和 src/main/filters/prod 这对应pom文件中的Profiles。分别在dev 和 prod 文件夹中添加config.properties,配置各自的环境。我的prod/config.properties配置内容是:
# postgresql
jdbc.driverClassName=org.postgresql.Driver
jdbc.url=jdbc:postgresql://ip:port/releasenote
jdbc.username=xxx
jdbc.password=xxx
#hibernate
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
在dev/config.properties添加相应的内容。
2.3 pom.xml 和 数据库文件
pom文件过长,就直接放链接啦。
创建文件夹src/main/resources/db,其中放sql文件。
创建数据库releasenote,三张表TB_MD_RELEASE_NOTE,TB_USER,TB_ISSUE。
-- mysql 8
create database if not exists releasenote;
use releasenote;
drop table if exists TB_MD_RELEASE_NOTE;
CREATE TABLE TB_MD_RELEASE_NOTE(
OBJ_ID bigint PRIMARY key AUTO_INCREMENT,
VERSION VARCHAR(20),
CONTENT text,
PUBLISH_DATE TIMESTAMP);
insert into TB_MD_RELEASE_NOTE (`version`, `content`, publish_date)
values ('0.0.0', '# 0.0.0', '2021-4-23 13:12:34'),('0.0.1', '# 0.0.1', '2021-4-23 14:12:34');
use releasenote;
create table TB_USER(
OBJ_ID bigint PRIMARY key AUTO_INCREMENT,
username varchar(20));
create table TB_ISSUE(
OBJ_ID BIGINT PRIMARY key AUTO_INCREMENT,
title varchar(40),
content text,
create_time timestamp,
user_ID bigint);
insert INTO TB_USER(`username`)
VALUES ("init"),
("backup");
insert into TB_ISSUE(`title`, `content`, `create_time`, `user_ID`)
VALUES ("can not plot scopy for m15", "for m15, when plot SN xxxxx, get error.", "2021-5-12 14:05:34", 1), ("IPS seems very useful, how about ....", "the method of calculate ... is ...", "2021-5-17 14:22:43", 1 );
2.4 spring配置
新建文件src/main/resources/config.properties,在其中放置前面filters文件中的配置,如下:
# common variables
dataPath = /releasenote
siteDisplay=CN
websiteVesion=1.0.0
# jdbc
jdbc.driverClassName=${jdbc.driverClassName}
jdbc.url = ${jdbc.url}
# authentication
jdbc.username=${jdbc.username}
jdbc.password=${jdbc.password}
# hibernate
hibernate.connection.autocommit=false
hibernate.format_sql=true
hibernate.show_sql=false
hibernate.hbm2ddl.auto=update
hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
hibernate.dialect=${hibernate.dialect}
新建文件夹src/main/resources/META-INF,在META-INF中新建文件applicationContext.xml。配置spring以及hibernate。
applicationContext.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:jee="http://www.springframework.org/schema/jee"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
">
<!--********************************************配置Spring***************************************-->
<!-- 自动扫描 -->
<context:component-scan base-package="com.example">
<!-- 扫描时跳过 @Controller 注解的JAVA类(控制器) -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--********************************************配置hibernate********************************************-->
<!--扫描配置文件(这里指向的是之前配置的那个config.properties)-->
<context:property-placeholder location="classpath:/config.properties" />
<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" 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}" />
<!--密码-->
<!-- druid-->
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1"/>
<property name="minIdle" value="1"/>
<property name="maxActive" value="3"/>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
</bean>
<!-- 配置entityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.example"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
</bean>
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop> <!--hibernate根据实体自动生成数据库表 validate/update/create-->
<prop key="hibernate.dialect">${hibernate.dialect}</prop> <!--指定数据库方言-->
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop> <!--在控制台显示执行的数据库操作语句-->
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop> <!--在控制台显示执行的数据哭操作语句(格式)-->
</props>
</property>
</bean>
<!-- 事物管理器配置 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!-- Spring Data Jpa配置 -->
<jpa:repositories base-package="com.example.dao" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager"/>
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
2.5 Spring-mvc 配置
spring-mvc配置:1,启动注解,使用默认的servlet处理,并扫描controller类; 2,配置视图解析,增加视图的前缀和后缀; 3,对静态资源的映射; 4,转换器配置,将返回的对象转为json格式数据; 5,开启文件上传配置;
src/main/resources/META-INF/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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" 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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 启动注解驱动的spring MVC功能,注册请求url和注解POJO类方法的映射-->
<mvc:annotation-driven />
<!-- use @Value("${}") in Bean-->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:config.properties" />
</bean>
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<!-- 只扫描Controller注解的类 -->
<context:component-scan base-package="com.seagate.flawscananalytic.controller">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<mvc:default-servlet-handler />
<!-- 视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="views/" />
<!-- 前缀 -->
<property name="suffix" value=".jsp" />
<!-- 后缀 -->
</bean>
<!--这里是对静态资源的映射-->
<!-- <mvc:resources mapping="/js/**" location="/resources/js/" />
<mvc:resources mapping="/css/**" location="/resources/css/" />
<mvc:resources mapping="/img/**" location="/resources/img/" /> -->
<mvc:resources mapping="/assets/**" location="/resources/" />
<!-- to show uploaded images, maybe not suitable-->
<mvc:resources mapping="/images/**" location="/data/images/"></mvc:resources>
<!-- 通过实体类返回json格式数据的关键配置 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<!--upload by multipart/form-data -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- one of the properties available; the maximum file size in bytes -->
<property name="maxUploadSize" value="104857600" />
</bean>
</beans>
2.6 web.xml
1,加载spring配置;2,spring监听器;3,spring-mvc配置;4,字符集
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<welcome-file-list>
<welcome-file>/index.jsp</welcome-file>
</welcome-file-list>
<!--加载Spring的配置文件到上下文中去-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:META-INF/applicationContext.xml
</param-value>
</context-param>
<!-- Spring监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 字符集过滤 -->
<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>
<!-- spring MVC config start-->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:META-INF/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2.7 mvc
以releasenote表为例,src/main/java/com/example/entity/ReleaseNote.java:
package com.example.entity;
import java.sql.Timestamp;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.hibernate.annotations.Type;
@Entity
@Table(name = "TB_MD_RELEASE_NOTE")
@SequenceGenerator(name = "TB_MD_RELEASE_NOTE_OBJ_ID_SEQ", sequenceName = "TB_MD_RELEASE_NOTE_OBJ_ID_SEQ", allocationSize = 1, initialValue = 24)
public class ReleaseNote {
@Id
@Column(name = "OBJ_ID", unique = true, nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TB_MD_RELEASE_NOTE_OBJ_ID_SEQ")
// @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long objId;
@Column(name = "\"VERSION\"")
private String version;
@Column(name = "\"CONTENT\"")
@Type(type = "text")
private String content;
@Column(name = "PUBLISH_DATE")
private Timestamp publishDate;
/*getter,setter,toString省略了*/
}
src/main/java/com/example/util/TimeUtil.java:
package com.example.util;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
public class TimeUtil {
static DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String timestampToString(Timestamp time) {
return df.format(time);
}
/**
* convert string time to timestamp
*
* @param time 5/11/2021 12:21:02 PM or 2021-5-11 12:21:02
* @return Timestamp
*/
public static Timestamp stringToTimestamp(String time) {
Date date = null;
int[] datePart = null, timePart = null;
String[] parts = time.split(" ");
int year, month, day;
if (time.contains("/")) {
if (parts.length == 3) {
String APMarker = parts[2];
datePart = Arrays.stream(parts[0].split("/")).mapToInt(Integer::parseInt).toArray();
timePart = Arrays.stream(parts[1].split(":")).mapToInt(Integer::parseInt).toArray();
if (APMarker.equals("PM")) {
timePart[0] = timePart[0] + 12;
if (timePart[0] >= 24) {
datePart[1] += 1;
}
}
year = datePart[2];
month = datePart[0];
day = datePart[1];
} else {
System.out.println("can not convert string:" + time + " to Timestamp, wrong former");
return null;
}
} else if (time.contains("-")) {
if (parts.length == 2) {
datePart = Arrays.stream(parts[0].split("-")).mapToInt(Integer::parseInt).toArray();
timePart = Arrays.stream(parts[1].split(":")).mapToInt(Integer::parseInt).toArray();
year = datePart[0];
month = datePart[1];
day = datePart[2];
} else {
System.out.println("can not convert string:" + time + " to Timestamp, wrong former");
return null;
}
} else {
System.out.println("can not convert string:" + time + " to Timestamp, wrong former");
return null;
}
Calendar c = Calendar.getInstance();
c.set(year, month - 1, day, timePart[0], timePart[1], timePart[2]);
date = c.getTime();
return new Timestamp(date.getTime());
}
/**
* formate time string from 5/11/2021 12:21:02 PM to 2021-5-12 00:21:02
*
* @param date
* @return
*/
public static String formatString(String date) {
Timestamp time = stringToTimestamp(date);
return timestampToString(time);
}
public static String getCurrentTime() {
String time = df.format(new Date());
return time;
}
}
src/main/java/com/example/bean/ReleaseNoteBean.java:
package com.example.bean;
import com.example.entity.ReleaseNote;
import com.example.util.TimeUtil;
public class ReleaseNoteBean implements Comparable<ReleaseNoteBean> {
private Long objId;
private String version;
private String content;
private String publishdate;
public static String strFormat = "yyyy-MM-dd HH:mm:ss";
public void fromDto(ReleaseNote releasenote) {
this.objId = releasenote.getObjId();
this.version = releasenote.getVersion();
this.content = releasenote.getContent();
this.publishdate = TimeUtil.timestampToString(releasenote.getPublishDate());
}
public ReleaseNote toDto() {
ReleaseNote releaseNote = new ReleaseNote(); // ? is this one not controlled by bean factory
try {
releaseNote.setObjId(this.objId);
} catch (NullPointerException e) {
System.out.println("NullPointerException: ReleaseNoteBean.objId");
}
releaseNote.setVersion(this.version);
releaseNote.setContent(this.content);
releaseNote.setPublishDate(TimeUtil.stringToTimestamp(this.publishdate));
return releaseNote;
}
@Override
public int compareTo(ReleaseNoteBean bean) {
int compareResult = 0;
String[] versionArr = this.version.split("\\.");
String[] otherVersionArr = bean.getVersion().split("\\.");
if (versionArr.length > otherVersionArr.length) {
String[] newOtherVersionArr = new String[versionArr.length];
for (int i = 0; i < otherVersionArr.length; i++) {
newOtherVersionArr[i] = otherVersionArr[i];
}
for (int i = otherVersionArr.length; i < versionArr.length; i++) {
newOtherVersionArr[i] = "0";
}
otherVersionArr = newOtherVersionArr;
}
if (versionArr.length < otherVersionArr.length) {
String[] newVersionArr = new String[otherVersionArr.length];
for (int i = 0; i < versionArr.length; i++) {
newVersionArr[i] = versionArr[i];
}
for (int i = versionArr.length; i < otherVersionArr.length; i++) {
newVersionArr[i] = "0";
}
versionArr = newVersionArr;
}
for (int i = 0; i < versionArr.length; i++) {
if (Integer.valueOf(versionArr[i]) < Integer.valueOf(otherVersionArr[i])) {
compareResult = -1;
break;
}
if (Integer.valueOf(versionArr[i]) > Integer.valueOf(otherVersionArr[i])) {
compareResult = 1;
break;
}
}
return compareResult;
}
/* getter,settring,toString*/
}
src/main/java/com/example/dao/ReleaseNoteDao.java:
package com.example.dao;
import java.util.List;
import com.example.entity.ReleaseNote;
public interface ReleaseNoteDao {
public void saveReleaseNote(ReleaseNote releaseNote);
public ReleaseNote getReleaseNote(Long objId);
public List<ReleaseNote> getAllReleaseNotes();
public void deleteReleaseNote(Long objId);
}
src/main/java/com/example/dao/impl/ReleaseNoteDaoImpl.java:
package com.example.dao.impl;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import com.example.dao.ReleaseNoteDao;
import com.example.entity.ReleaseNote;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository("ReleaseNoteDao")
public class ReleaseNoteDaoImpl implements ReleaseNoteDao {
@PersistenceContext // (unitName = "entityManagerFactory")
EntityManager em;
@Transactional
public void saveReleaseNote(ReleaseNote releaseNote) {
if (releaseNote.getObjId() == null)
em.persist(releaseNote);
else
em.merge(releaseNote);
}
@Transactional
public ReleaseNote getReleaseNote(Long objId) {
ReleaseNote releaseNote = null;
releaseNote = em.find(ReleaseNote.class, objId);
return releaseNote;
}
@Transactional
public List<ReleaseNote> getAllReleaseNotes() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<ReleaseNote> q = cb.createQuery(ReleaseNote.class);
Root<ReleaseNote> root = q.from(ReleaseNote.class);
q.distinct(true);
q.select(root);
q.orderBy(cb.asc(root.get("objId")));
return em.createQuery(q).getResultList();
}
@Transactional
public void deleteReleaseNote(Long objId) {
ReleaseNote releaseNote = getReleaseNote(objId);
if (releaseNote != null) {
em.remove(releaseNote);
} else {
System.out.println("try to delete null;objiId:" + String.valueOf(objId));
}
}
}
2.8 用Junit4测试
先写一个基础测试类,src/main/test/java/com/example/BaseTestCaseJunit44.java:
package com.example;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:META-INF/applicationContext.xml", "classpath:META-INF/spring-mvc.xml" })
@Transactional(transactionManager = "transactionManager")
@Rollback(value = true)
public class BaseTestCaseJunit44 {
// test function can not accept args
@Rule
public TestName name = new TestName();
@Before
public void before() {
System.out.println("********************" + " START ***************************");
System.out.println("Test: " + name.getMethodName());
}
@After
public void after() {
System.out.println("********************" + " END ***************************");
}
@Test
public void testBase() {
}
}
然后写一个dao的测试,src/main/test/java/com/example/DaoTest.java:
package com.example;
import com.example.bean.ReleaseNoteBean;
import com.example.dao.ReleaseNoteDao;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
public class DaoTest extends BaseTestCaseJunit44 {
@Autowired
ReleaseNoteDao releaseNoteDao;
@Test
public void testInit() {
}
@Test
public void testReleaseNoteGetOne() {
Long objId = releaseNoteDao.getAllReleaseNotes().get(0).getObjId();
releaseNoteDao.getReleaseNote(objId);
}
@Test
public void testReleaseNoteSave() {
ReleaseNoteBean releaseNote = new ReleaseNoteBean();
releaseNote.setVersion("t.t.t");
releaseNote.setContent("# Release t.t.t");
releaseNote.setPublishdate("2021-5-13 13:13:13");
releaseNoteDao.saveReleaseNote(releaseNote.toDto());
}
@Test
public void testReleaseNoteDelete() {
Long objId = releaseNoteDao.getAllReleaseNotes().get(0).getObjId();
releaseNoteDao.deleteReleaseNote(objId);
}
}
在控制台中,切到当前项目所在目录,执行mvn test:
xxx@xxx MINGW64 /c/CodeProjects/Java/isolate/demos/ssh_demo (dev)
$ mvn test test (default-test) on project demo
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------------< com.example:demo >-------------------------- eports for the individual test resu
[INFO] Building demo Maven Webapp 1.0-SNAPSHOT
[INFO] --------------------------------[ war ]--------------------------------- and [date].dumpstream.
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO] Copying 3 resources
[INFO] Copying 11 resources following articles:
[INFO] n
[INFO] --- maven-compiler-plugin:2.4:compile (default-compile) @ demo ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ demo
---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:2.4:testCompile (default-testCompile) @ demo ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ demo ---
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.DaoTest
******************** START ***************************
Test: testInit
******************** END ***************************
******************** START ***************************
Test: testReleaseNoteSave
******************** END ***************************
******************** START ***************************
Test: testReleaseNoteDelete
******************** END ***************************
******************** START ***************************
Test: testReleaseNoteGetOne
******************** START ***************************
Test: testBase
******************** END ***************************
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.872 s - in c[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.721 s
[INFO] Finished at: 2021-05-25T08:56:20+08:00
[INFO] ------------------------------------------------------------------------
配置完成。
2.9 controller以及视图
src/main/java/com/example/service/ReleaseNoteService.java:
package com.example.service;
import java.util.List;
import com.example.bean.ReleaseNoteBean;
public interface ReleaseNoteService {
public Long saveReleaseNote(ReleaseNoteBean releaseNote);
public ReleaseNoteBean getReleaseNote(Long objId);
public List<ReleaseNoteBean> getAll();
public void deleteReleaseNote(Long objId);
}
src/main/java/com/example/service/impl/ReleaseNoteServiceImpl.java:
package com.example.service.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.example.bean.ReleaseNoteBean;
import com.example.dao.ReleaseNoteDao;
import com.example.entity.ReleaseNote;
import com.example.service.ReleaseNoteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("releaseNoteServiceImpl")
@Transactional
public class ReleaseNoteServiceImpl implements ReleaseNoteService {
@Autowired
@Qualifier("ReleaseNoteDao")
ReleaseNoteDao releaseNoteDao;
@Transactional
public Long saveReleaseNote(ReleaseNoteBean releaseNoteBean) {
ReleaseNote releaseNote = releaseNoteBean.toDto();
releaseNoteDao.saveReleaseNote(releaseNote);
return releaseNote.getObjId();
}
@Transactional
public ReleaseNoteBean getReleaseNote(Long objId) {
ReleaseNote releaseNote = releaseNoteDao.getReleaseNote(objId);
ReleaseNoteBean releaseNoteBean = new ReleaseNoteBean();
releaseNoteBean.fromDto(releaseNote);
return releaseNoteBean;
}
@Transactional
public List<ReleaseNoteBean> getAll() {
List<ReleaseNoteBean> releaseNoteBeans = new ArrayList<ReleaseNoteBean>();
List<ReleaseNote> releaseNotes = releaseNoteDao.getAllReleaseNotes();
for (ReleaseNote releaseNote : releaseNotes) {
ReleaseNoteBean releaseNoteBean = new ReleaseNoteBean();
releaseNoteBean.fromDto(releaseNote);
releaseNoteBeans.add(releaseNoteBean);
}
Collections.sort(releaseNoteBeans);
return releaseNoteBeans;
}
@Transactional
public void deleteReleaseNote(Long objId) {
releaseNoteDao.deleteReleaseNote(objId);
}
}
相关依赖文件:ActionResult.java, Host.java, imageController.java
src/main/java/com/example/controller/ReleaseNoteController.java:
package com.example.controller;
import java.io.File;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson.JSON;
import com.example.bean.ActionResult;
import com.example.bean.ReleaseNoteBean;
import com.example.service.ReleaseNoteService;
import com.example.util.Host;
import com.example.util.TimeUtil;
@Controller
@RequestMapping("/")
public class ReleaseNoteController {
@Value("${siteDisplay}")
private String siteDisplay;
@Value("${websiteVesion}")
private String websiteVesion;
@Autowired // (name = "releaseNoteServiceImpl")
private ReleaseNoteService releaseNoteService;
@RequestMapping(value = "ReleaseNoteAdmin.do", method = RequestMethod.GET)
public String releaseNoteAdmin(Model model, HttpServletRequest request) throws UnknownHostException {
InetAddress addr = InetAddress.getByName(request.getRemoteAddr());
String id = Host.getId(addr);
model.addAttribute("uid", id);
List<ReleaseNoteBean> releaseNotes = releaseNoteService.getAll();
model.addAttribute("siteDisplay", siteDisplay);
model.addAttribute("websiteVesion", websiteVesion);
model.addAttribute("releaseNotes", releaseNotes);
return "releaseNote/mdReleaseNoteAdmin";
}
@RequestMapping(value = "ReleaseNoteMain.do", method = RequestMethod.GET)
public String releaseNoteMain(Model model, HttpServletRequest request) throws Exception, UnknownHostException {
InetAddress addr = InetAddress.getByName(request.getRemoteAddr());
String id = Host.getId(addr);
model.addAttribute("uid", id);
List<ReleaseNoteBean> releaseNotes = releaseNoteService.getAll();
model.addAttribute("siteDisplay", siteDisplay);
model.addAttribute("websiteVesion", websiteVesion);
model.addAttribute("releaseNotes", releaseNotes);
return "releaseNote/mdReleaseNote";
}
@RequestMapping(value = "getReleaseNotesList.do", method = RequestMethod.POST)
@ResponseBody
public ActionResult releaseNotesList(Model model) throws Exception {
ActionResult result = new ActionResult(true);
List<ReleaseNoteBean> releaseNotes = releaseNoteService.getAll();
result.setRtnVal(releaseNotes);
result.setSuccess(true);
return result;
}
@RequestMapping(value = "editor.do", method = RequestMethod.GET)
public String editor(Model model, @RequestParam("objId") Long objId) {
model.addAttribute("releasenote_id", objId);
return "releaseNote/mdReleaseNoteEditor";
}
@RequestMapping(value = "getReleaseNote.do", method = RequestMethod.POST)
public @ResponseBody ActionResult getReleaseNote(Model model, @RequestParam("objId") Long objId) throws Exception {
ActionResult result = new ActionResult(true);
ReleaseNoteBean releasenote = releaseNoteService.getReleaseNote(objId);
result.setRtnVal(releasenote);
result.setSuccess(true);
return result;
}
@ResponseBody
@RequestMapping(value = "releaseNote/image/upload.do", method = RequestMethod.POST)
public String uploadImage(HttpServletRequest req, @RequestParam(value = "guid") String guid,
@RequestParam(value = "editormd-image-file", required = true) MultipartFile file,
HttpServletRequest request) throws Exception {
String trueFileName = file.getOriginalFilename();
String suffix = trueFileName.substring(trueFileName.lastIndexOf("."));
String fileName = System.currentTimeMillis() + "_" + guid + suffix;
// String path = "C:/flawScanAnalytic/releaseNote/images";
ServletContext servletContext = req.getServletContext();
String path = servletContext.getRealPath("/");
System.out.println(path);
// ..src/main/app/data/images/xx.png(/jpg/..)
File targetFile = new File(path, "data/images/" + fileName);
if (!targetFile.exists()) {
targetFile.mkdirs();
}
try {
file.transferTo(targetFile);
// ImageUtil.reSize(targetFile, targetFile, 320, 320, true);
} catch (Exception e) {
e.printStackTrace();
}
Map<String, Object> json = new HashMap<String, Object>();
json.put("url", "images/" + fileName);
json.put("success", 1);
json.put("message", "upload success!");
String result = JSON.toJSONString(json);
return result;
}
@RequestMapping(value = "releaeNote/updateReleaseNote.do", method = RequestMethod.POST)
public @ResponseBody ActionResult saveReleaseNote(@RequestParam("objId") Long objId,
@RequestParam("content") String content) throws Exception {
ActionResult rst = new ActionResult(false);
ReleaseNoteBean releaseNoteBean = releaseNoteService.getReleaseNote(objId);
if (releaseNoteBean == null) {
throw new Exception(" objid:" + objId + " not exists");
}
releaseNoteBean.setContent(content);
releaseNoteService.saveReleaseNote(releaseNoteBean);
rst.setSuccess(true);
return rst;
}
@RequestMapping(value = "releaeNote/newReleaseNote.do", method = RequestMethod.POST)
public @ResponseBody ActionResult newReleaseNote(@RequestParam("version") String version,
@RequestParam("content") String content, @RequestParam("publishDate") String publishDT) throws Exception {
ActionResult rst = new ActionResult(false);
ReleaseNoteBean releaseNoteBean = new ReleaseNoteBean();
releaseNoteBean.setVersion(version);
releaseNoteBean.setContent(content);
// publishDT "4/24/2021 2:38:10 PM"
publishDT = TimeUtil.formatString(publishDT);
releaseNoteBean.setPublishdate(publishDT);
releaseNoteService.saveReleaseNote(releaseNoteBean);
rst.setSuccess(true);
return rst;
}
@RequestMapping(value = "releaeNote/deleteReleaseNote.do", method = RequestMethod.POST)
public @ResponseBody ActionResult deleteReleaseNote(@RequestParam("objId") Long objId) throws Exception {
ActionResult rst = new ActionResult(false);
ReleaseNoteBean releaseNoteBean = releaseNoteService.getReleaseNote(objId);
if (releaseNoteBean == null) {
throw new Exception(" objid:" + objId + " not exists");
}
releaseNoteService.deleteReleaseNote(objId);
rst.setSuccess(true);
return rst;
}
}
2.10 编写视图并测试
在src/main/webapp/views/releaseNote
中创建视图mdReleaseNote.jsp,mdReleaseNoteAdmin.jsp,mdReleaseNoteEditor.jsp,mdReleaseNoteList.jsp.
在src/main/webapp/views/common
中创建通用的一些视图backToTop.jsp,contextMenu.jsp,mdEditor.jsp,navbar.jsp.
在src/main/webapp/resources
中添加静态资源。
在src/main/webapp/resources/js/myJS
中添加自己写的脚本mdReleaseNoteContext.js.
在.vscod
中创建task.json:
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Jetty debug",
"type": "shell",
"command": "mvn jetty:run",
"group": "build",
"isBackground": false,
"problemMatcher": [],
"options": {
"env": {
"maven_opts": "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000"
}
}
},
{
"label": "dev tomcat debug",
"type": "shell",
"command": "mvn jetty:run -Pdev-tomcat",
"group": "build",
"isBackground": false,
"problemMatcher": [],
"options": {
"env": {
"maven_opts": "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000"
}
}
},
{
"label": "prod debug",
"type": "shell",
"command": "mvn jetty:run -Pprod",
"group": "build",
"isBackground": false,
"problemMatcher": [],
"options": {
"env": {
"maven_opts": "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000"
}
}
},
]
}
launch.json:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Debug (Attach) - Remote",
"request": "attach",
"hostName": "localhost",
"port": 8000
}
]
}
在vscode中使用命令tasks:run build task, 然后到debug,选择Debug(Attach) - Remote,开始调试,或者直接F5开始调试。
如图:
计划通。
2.11 slf4j和log4j2
添加log,slf4j+log4j2. 所需要的依赖已经在上方pomxml里面了
在src/main/resources/META-INF 和 src/main/resources下都添加文件log4j2.xml
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="WARN" monitorInterval="30">
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置-->
<console name="Console" target="SYSTEM_OUT">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%msg:日志消息,%n是换行符-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<File name="log" fileName="/releasenote/logs/test.log" append="false">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
</File>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="/releasenote/logs/info.log" filePattern="/releasenote/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<RollingFile name="RollingFileWarn" fileName="/releasenote/logs/debug.log" filePattern="/releasenote/logs/$${date:yyyy-MM}/debug-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
<RollingFile name="RollingFileError" fileName="/releasenote/logs/error.log" filePattern="/releasenote/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="com.seagate.flawscananalytic" level="INFO"></logger>
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<logger name="org.hibernate" level="INFO"></logger>
<!-- <logger name="org.hibernate.type" level="TRACE"></logger> -->
<!-- <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"></logger>
<logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG"></logger> -->
<!-- <logger name="org.hibernate.SQL" level="TRACE"></logger> -->
<!-- <logger name="org.hibernate.engine.QueryParameters" level="DEBUG"></logger>
<logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG"></logger> -->
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers>
</configuration>
测试一下。在src/test/java/com/example下新建utilTest.java.
utilTest.java:
package com.example;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class utilTest extends BaseTestCaseJunit44 {
// int test, will use src/main/resources/log4j2.xml
static final Logger logger = LoggerFactory.getLogger(utilTest.class);
@Test
public void testLog() {
logger.trace("trace message");
logger.debug("debug message");
logger.info("info message");
logger.warn("warn message");
logger.error("error message");
// logger.fatal("fatal message");
}
@Test
public void testUtil() {
}
}
运行单元测试。
console的输出:
文件的输出/releasenote/logs:
xxx@xxx MINGW64 /c/releasenote/logs
$ tree .
.
|-- 2021-05
| |-- debug-2021-05-20-1.log
| |-- error-2021-05-20-1.log
| `-- info-2021-05-20-1.log
|-- debug.log
|-- error.log
|-- info.log
`-- test.log
1 directory, 7 files
通。
碰到的问题
3.1 junit 错误
3.1.1 No tests found matching
第一次是因为使用的junit的版本不对。spring5 要求junit的版本要大于等于4.12.
报错的具体内容如下:
initializationError
Failed
0.01s
Message:
N/A
Stack trace:
java.lang.Exception: No tests found matching [{ExactMatcher:fDisplayName=testBase], {ExactMatcher:fDisplayName=testBase(com.example.BaseTestCaseJunit44)], {LeadingIdentifierMatcher:fClassName=com.example.BaseTestCaseJunit44,fLeadingIdentifier=testBase]] from org.junit.internal.requests.ClassRequest@3c72f59f
at org.junit.internal.requests.FilterRequest.getRunner(FilterRequest.java:35)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.createFilteredTest(JUnit4TestLoader.java:83)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.createTest(JUnit4TestLoader.java:74)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.loadTests(JUnit4TestLoader.java:49)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:513)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:756)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:452)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
3.1.2 Class not found
后面还有一次是因为执行了mvn clean 后立马进行单元测试。
Class not found com.example.BaseTestCaseJunit44
java.lang.ClassNotFoundException: com.example.BaseTestCaseJunit44
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
URLClassLoader.java:382
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
ClassLoader.java:424
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
Launcher.java:349
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
ClassLoader.java:357
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.loadClass(RemoteTestRunner.java:766)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.loadClasses(RemoteTestRunner.java:490)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:513)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:756)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:452)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
使用mvn test.
3.2 配置xml文件时碰到的问题
3.2.1 dao层没法获取session
起初hibernatenate使用的时sessionFactory的方式执行操作。配置spring的时候,扫描组件,直接扫描了所有的,然后就报了这个错。(复现不出来了😂)
通过扫描的时候排除Controller注解就行了。
<!--********************************************配置Spring***************************************-->
<!-- 自动扫描 -->
<context:component-scan base-package="com.example">
<!-- 扫描时跳过 @Controller 注解的JAVA类(控制器) -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
参考:Spring MVC注解方式service和controller的扫描顺序
3.2.2 连接Mysql8时url解析失败问题
通常情况下,连接MySQL8时的连接需要加上时区等几个参数。这个url我配置在config.properties文件中。
jdbc.url=jdbc:mysql://localhost:3306/releasenote?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
这在config.properties文件没问题,但是当进行测试,加载xml配置文件的时候就出了问题,因为xml中的&
需要写成&
不然会解析失败。但是在config.properties中出现;
会分行。
最后解决办法就是只保留一个参数,写成jdbc.url=jdbc:mysql://localhost:3306/releasenote?serverTimezone=UTC
。
也能运行。 但是应该会在某些情况下碰到问题。凑合着先用吧。
3.2.3 注入transactionManager失败
起初使用sessionFactory,后来改成entityManger,property也改成entityManagerFactory了,但是测试的时候,报错没法注入transactionManager,由于entityManager没getter,setter方法。
错误地地方是,transactionManager的类错了。
- 当使用entityManagerFactory的方式时,transactionManager的类为
org.springframework.orm.jpa.JpaTransactionManager
,属性为:entityManagerFactory
, - 当使用sessionFactory的方式,transactionManager的类为:
org.springframework.orm.hibernate5.HibernateTransactionManager
,属性为sessionFactory
。
3.3 JPA 问题
3.3.1 一对多映射,错误的删除了所有的记录
使用中entity:User 和 entity:Issue存在一对多映射,在创建entity的时候进行了双向绑定,但是错误的使用级联 All,导致删除一个Issue记录会同时把从属与同一个User下所有的Issue以及User本身都删掉了。
改正后的:
#Entity User:
...
@OneToMany(cascade = CascadeType.ALL, targetEntity = Issue.class, fetch = FetchType.LAZY, mappedBy = "user")
List<Issue> issues;
...
#Entity Issue:
...
@ManyToOne(targetEntity = User.class, cascade = { CascadeType.MERGE, CascadeType.REFRESH }, optional = false)
@JoinColumn(name = "userID") // referencedColumnName default is the pk
User user;
...
cascade表示级联的选项;fetch表示更新的时机;mapperBy表示关系由哪个维护,此例中表示由Entity Issue中的user维护;targetEntity表示关系映射到哪个entity;optional,表示是否可以为空。
3.4 其他一些问题
3.4.1 如何获取客户端主机名
通过javax.servlet.ServletRequest.getRemoteAddr() 获取客户端IP,然后通过Java.net.InetAddress.getByName(IP)生成Java.net.InetAddress对象,最后通过Java.net.InetAddress.getHostName()获得主机名。
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@RequestMapping(value="sompage",method=RequestMethod.GET)
public @ResponseBody String control(HttpServletRequest request) throws UnknownHostException{
InetAddress addr = InetAddress.getByName(request.getRemoteAddr());
String hostName = addr.getHostName();
return hostName;
}
3.4.2 图片上传
上传文件需要依赖的两个类:
<!-- multipartResolver dependency-->
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${fileupload.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commonsio.version}</version>
</dependency>
首先要在spring-mvc配置文件中配置好上传文件的bean:
<!--upload by multipart/form-data -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- one of the properties available; the maximum file size in bytes -->
<property name="maxUploadSize" value="104857600" />
</bean>
然后在controller定义好文件上传的映射:
@RequestMapping(value = "image/upload.do", method = RequestMethod.POST)
@ResponseBody
public String uploadMap(@RequestParam(value = "editormd-image-file", required = true) MultipartFile file,
HttpServletRequest request) {
Map<String, Object> json = new HashMap<String, Object>();
int success = 0;
String msg = "";
if (file.isEmpty()) {
log.info("empty file");
msg = "empty file";
json.put("success", success);
json.put("message", msg);
String result = JSON.toJSONString(json);
return result;
} else {
log.info("===================image upload=====================");
log.info("size: " + file.getSize());
log.info("type: " + file.getContentType());
log.info("name: " + file.getName());
log.info("originalName: " + file.getOriginalFilename());
log.info("========================================");
}
String trueFileName = file.getOriginalFilename();
String curTime = TimeUtil.getCurrentTime().replace(" ", "_");
curTime = curTime.replace(":", "-");
String suffix = trueFileName.substring(trueFileName.lastIndexOf("."));
String fileName = trueFileName.split("\\.")[0].replace(" ", "_") + "_" + curTime + suffix;
String imagePath = dataPath + "/images";
File imageFolder = new File(imagePath);
if (!imageFolder.exists()) {
imageFolder.mkdirs();
}
File targetFile = new File(imagePath, fileName);
log.info("file: " + imagePath + '/' + fileName);
// 保存
try {
FileUtils.copyInputStreamToFile(file.getInputStream(), targetFile);
} catch (Exception e) {
log.error(e.getLocalizedMessage());
log.error("save image faile");
msg = "save image faile";
json.put("success", success);
json.put("message", msg);
String result = JSON.toJSONString(json);
return result;
}
success = 1;
msg = "upload success!";
json.put("url", "image/" + fileName);
json.put("success", success);
json.put("message", msg);
String result = JSON.toJSONString(json);
return result;
}
文件上传后会保存在一个固定的路径下,起初通过HttpServletRequest.getServletContext().getRealPath()获取相对项目的路径,但是担心更新发布后,这些文件会不小心删掉,就改用固定的路径。
文件显示的映射:
@RequestMapping(value = "image/**", method = RequestMethod.GET)
public void getMapPic1(HttpServletRequest request, HttpServletResponse httpResponse) {
String url = request.getRequestURI();
String filename = url.substring(url.lastIndexOf('/'));
String suffix = filename.substring(filename.lastIndexOf(".")).toLowerCase();
String imagePath = dataPath + "/images";
String filePath = imagePath + '/' + filename;
log.info("filePath:" + filePath);
File file = new File(filePath);
FileInputStream fis = null;
try {
String type = "jpeg";
if (contentType.containsKey(suffix))
type = contentType.get(suffix);
httpResponse.setContentType("image/" + type);
OutputStream out = httpResponse.getOutputStream();
fis = new FileInputStream(file);
byte[] b = new byte[fis.available()];
fis.read(b);
out.write(b);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
通过流的方式获取图片。在之前相对项目路径的保存文件的情况下,可以在spring-mvc的配置中添加相应的静态资源映射就好了。
3.5 未解决的问题
3.5.1 Repository不能注入
想在项目中使用Repository的方式写dao,但是进行测试时,报错没有办法注入XXXRepository,找不到实现类。
看网上说是没扫描的,但是我确实添加了对Repository的扫描,componentScan 扫描了Repository,也添加了对repository的配置<jpa:repositories base-package="com.seagate.flawscananalytic.dao" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager"/>
.
破案:
正确的写法:
<jpa:repositories base-package="com.seagate.flawscananalytic.dao" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager"></jpa:repositories >
.
3.5.2 controller返回视图路径的问题
假设当前前端的url为/project/module1/page,
然后对page的controller类似如下:
...
RequestMapping("/)
class xxxController(){
...
@RequestMappint("module1/page")
public String page(...){
...
return 'module1/xxx'
}
...
}
在视图解析的配置中,我配置的前缀为’views/’,后缀为’.jsp’。在这种情况下,controller返回的映射路径为\project\module1\views\module1\xxx.jsp
,似乎映射的路径时跟当前路径有关。这好像是因为我配置的前缀是相对路径的原因。不知道前缀能不能写成绝对路径如/views/
。
还有其他的一些问题,bootstrap的,后端的想不起来。
参考
搭建这个项目中参考了很多大佬们的文章和项目,但是有些找不到了(对不住了-😂),此处一并感谢他们。