设置docker时区

docker容器时区修改方法

方法一 使用数据卷volume:

容器启动时加上 -v /etc/localtime:/etc/localtime, 容器就会与宿主机同一时区
若使用编排工具如docker-compose等时可在配置文件指定volume

方法二 直接更新容器配置:

在Dockerfile里指定

1
RUN echo "Asia/Shanghai" > /etc/timezone

Linux时区参考

Spring Boot 端口设置

本文介绍了Spring Boot设置Http端口的几种方式
Spring Boot默认端口为8080

指定端口

在Spring Boot配置文件application.propertites中设置server.port

1
server.port=8888

添加VM配置 -Dserver.port=8888 亦可指定配置

随机端口

1
server.port=0

获取端口号

使用@LocalServerPort注解可在运行时获取程序的Http端口号

1
2
3
4
5
6
7
8
9
10
11
12
13
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class MyWebIntegrationTests {
@Autowired
EmbeddedWebApplicationContext server;
@LocalServerPort
int port;
// ...
}

官方文档详见

Java时间转换-JodaTime转Date,Java8 JavaTime转Date

JodaTime转换

  • org.joda.time.LocalDate转java.util.Date
1
Date date = localDate.toDateTimeAtStartOfDay().toDate();
  • org.joda.time.LocalDateTime转java.util.Date
1
Date date = localDateTime.toDate();

Java8 JavaTime转换

  • java.time.localDate转java.util.Date
1
Date date = Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
  • java.time.LocalDateTime转java.util.Date
1
Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
  • java.time.ZonedDateTime转java.util.Date
1
Date date = Date.from(zonedDateTime.toInstant());

Hibernate枚举类型的映射

Hibernate枚举类型的映射

本文介绍一下Hibernate映射枚举类型的几种方式.

首先定义一个枚举.

1
2
3
4
5
6
7
8
9
10
11
12
13
public enum Gender {
male("男"),
female("女");
private String name;
private Gender(String name){
this.name = name;
}
public char getValue() {
return name;
}
}

基本 - 按枚举的ordinal属性映射

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
@Column
private String name;
// 没有额外注解 或 添加注解 @Enumerated(EnumType.ORDINAL)
@Column
// @Enumerated(EnumType.ORDINAL) // 可省略
private Gender gender;
// ...
}

按照这种写法, Hibernate会将Enum类型在声明中的顺序即ordinal属性映射成int型存入数据库, 对于枚举Gender来说, male在数据库中存的是 0, 而female是 1.
好处是简单, 缺点是枚举值多时可读性差, 若声明顺序不小心发生变化映射会出错且不易发现.

基本 - 按枚举的name属性映射

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
@Column
private String name;
@Column
@Enumerated(EnumType.STRING)
private Gender gender;
// ...
}

使用此方法数据库中存储的是枚举类型的name属性, 即male在数据库中存的就是’male’, female在数据库中存的就是’female’.
好处是同样也比较简单,可读性大于第一种

附EnumType源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Defines mapping for enumerated types. The constants of this
* enumerated type specify how a persistent property or
* field of an enumerated type should be persisted.
*
* @since Java Persistence 1.0
*/
public enum EnumType {
/** Persist enumerated type property or field as an integer. */
ORDINAL,
/** Persist enumerated type property or field as a string. */
STRING
}

灵活映射 - 使用Hibernate自定义类型

自定义类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class GenderType extends AbstractSingleColumnStandardBasicType<Gender> {
public static final GenderType INSTANCE = new GenderType();
public GenderType() {
super(
CharTypeDescriptor.INSTANCE,
GenderJavaTypeDescriptor.INSTANCE
);
}
public String getName() {
return "gender";
}
@Override
protected boolean registerUnderJavaType() {
return true;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class GenderJavaTypeDescriptor extends AbstractTypeDescriptor<Gender> {
public static final GenderJavaTypeDescriptor INSTANCE =
new GenderJavaTypeDescriptor();
protected GenderJavaTypeDescriptor() {
super( Gender.class );
}
public String toString(Gender value) {
return value == null ? null : value.name();
}
public Gender fromString(String string) {
return string == null ? null : Gender.valueOf( string );
}
public <X> X unwrap(Gender value, Class<X> type, WrapperOptions options) {
return CharacterTypeDescriptor.INSTANCE.unwrap(
value == null ? null : value.getValue(),
type,
options
);
}
public <X> Gender wrap(X value, WrapperOptions options) {
return Gender.fromString(
CharacterTypeDescriptor.INSTANCE.wrap( value, options )
);
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
@Column
private String name;
@Column
@Type( type = "org.hibernate.userguide.mapping.basic.GenderType" )
public Gender gender;
// ...
}

灵活映射 - 使用AttributeConverter

AttributeConverter可用于各种类型的自定义映射, 当然也包括了枚举类型.
AttributeConverter, 其中 X 是实体属性的类型, Y 是数据库字段的类型.

示例:
定义转换器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class GenderConverter implements AttributeConverter<Gender, String> {
// 将实体属性X转化为Y存储到数据库中
@Override
public String convertToDatabaseColumn(Gender attribute) {
if (attribute == null) {
return null;
}
return attribute.getValue();
}
// 将数据库中的字段Y转化为实体属性X
@Override
public Gender convertToEntityAttribute(String dbData) {
return Gender.fromString(dbData);
}
}

完善枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public enum Gender {
male("男"),
female("女");
private String value;
private Gender(String value){
this.value = value;
}
public static Gender fromString(String value) {
Gender gender = null;
switch(value): {
case "男":
gender = male;
break;
case "女":
gender = female;
break;
}
return gender;
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private String name;
@Column
@Convert(converter = GenderConverter.class)
private Gender gender;
// ...
}

属性转换器需要实现AttributeConverter接口,该接口可以完成实体中数据和数据库中数据的相互转换.

此时在数据库中对应存入的值为’男’,’女’

在实际项目中使用的枚举类型可能又有相似的地方,比如male(“男”), 有一个字符串参数, 需要使用该参数进行映射. 若有若干相似枚举类型, 每个枚举都写属性转换器太过麻烦. 此时可以定义通用接口, 相关枚举类型实现该接口, 然后将转换器AttributeConverter中的X设为该接口. 对应枚举再定义static class去继承通用转换器, 从而简化了代码.

Thymeleaf SAXParseException: The content of elements must consist of well-formed character data or markup

问题

使用Thymeleaf时遇到如下异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Caused by: org.xml.sax.SAXParseException: The content of elements must consist of well-formed character data or markup.
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203)
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.fatalError(ErrorHandlerWrapper.java:177)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:400)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:327)
at com.sun.org.apache.xerces.internal.impl.XMLScanner.reportFatalError(XMLScanner.java:1438)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.startOfMarkup(XMLDocumentFragmentScannerImpl.java:2637)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2735)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:606)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:510)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:848)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:777)
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1213)
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:643)
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl.parse(SAXParserImpl.java:327)
at org.thymeleaf.templateparser.xmlsax.AbstractNonValidatingSAXTemplateParser.doParse(AbstractNonValidatingSAXTemplateParser.java:209)
at org.thymeleaf.templateparser.xmlsax.AbstractNonValidatingSAXTemplateParser.parseTemplateUsingPool(AbstractNonValidatingSAXTemplateParser.java:134)
... 47 common frames omitted

原因

The content of elements must consist of well-formed character data or markup.通常是因为代码不符合XML语法规范,像是元素名称使用数字开头、包含空格等,如:

1
2
<0></0>
< a></a>

查看代码后, 发现在JavaScript代码中有下面的用法

1
if (width < 960) {

Thymeleaf在执行到 < 时会尝试去解析从而出错。

解决办法

使用CDATA避免其中的文本被解析

1
2
3
4
5
<script type="text/javascript">
/*<![CDATA[*/
...
/*]]>*/
</script>

Thymeleaf比较判断枚举类型

问题

使用Thymeleaf试图比较、判断枚举类型

1
2
<span th:if="${source.sourceType == 0}>test</span>
<span th:if="${source.sourceType == 'MOVIE'">test</span>

以上两种方式均不能成功

解决办法

1
2
3
<span th:if="${source.sourceType.name() == 'MOVIE'}>test</span>
// 或
<span th:if="${source.sourceType == T(me.gelu.SourceType).MOVIE}">test</span>

Java多环境配置文件的管理(使用Maven Profile)

例子

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<project ...>
...
<build>
<resources>
<directory>src/main/resources</directory>
<excludes>
<exclude>filters/*</exclude>
...
</excludes>
<filtering>true</filtering> <!-- 会用filter里的配置对resources文件里配置进行替换 -->
</resources>
<filters>
<filter>${basedir}/src/main/resources/application-${env}.properties</filter>
</filters>
</build>
<profiles>
<profile>
<id>dev</id>
<properties>
<env>dev</env>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>prod</id>
<properties>
<env>prod</env>
</properties>
</profile>
</profiles>
</project>

application.properties

1
jdbc.password=${jdbc.config.url}

filters/application-dev.properties

1
jdbc.config.url=123456

编译后target下application.properties里

1
jdbc.properties=123456

其他环境同理,编译时指定profile即可。如mvn clean install -P prod即会使用filters/application-prod.properties下的配置进行替换。

Java信号量-Semaphore

简介

信号量常常用于多线程的代码中,它维护了一个许可集合。提供同步机制来控制同时访问的个数。java.util.concurrent包下的Semaphore可以很容易的进行信号量控制。

Java 中Semaphore提供了两中构造函数:

1
2
3
Semaphore(int permits);
Semaphore(int permits, boolean fair);
// permits表示最大的许可数量。fair表示是否公平模式(即true表示线程要按FIFO获取机会,无该字段的构造函数或该值为false则表示线程随机顺序获取许可)。

Semaphore提供acquire()、release()来获取和释放许可。程序可通过acquire()获取一个许可,没有则等待直到release()释放一个许可。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
// 只有5个线程能同时访问
final Semaphore semaphore = new Semaphore(5);
// 模拟10个客户端访问
for (int index = 0; index < 10; index++) {
final int NO = index;
Runnable run = () -> {
try {
// 获取许可
semaphore.acquire();
System.out.println("Accessing: " + NO);
TimeUnit.SECONDS.sleep(10);
// 释放许可
semaphore.release();
//availablePermits()返回当前有多少个许可可被获取
System.out.println("-----------------" + semaphore.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
}
};
exec.execute(run);
}
exec.shutdown();
}
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Accessing: 0
Accessing: 1
Accessing: 3
Accessing: 2
Accessing: 4
-----------------1
Accessing: 5
-----------------1
Accessing: 6
-----------------1
-----------------1
Accessing: 7
-----------------1
Accessing: 9
Accessing: 8
-----------------1
-----------------2
-----------------3
-----------------4
-----------------5

模板方法模式

问题

现实中有流程确定而具体执行不知道的情况,此时可使用模板模式。

实现思路

定义一个抽象类实现部分逻辑并声明一些抽象方法给子类去实现(定义一个操作算法的骨架),当其子类以不同逻辑实现了这些抽象方法时就形成不同的行为(步骤延迟到子类)。使得子类可不改变一个算法的结构即可重定义该算法的某些特定步骤。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 模板类
public abstract class AbstractTemplate {
final void method1() {
...
method2();
...
}
abstract void method2();
final void method3() {...}
}
// 子类
public class ChildClass extends AbstractTemplate {
void method2() {
// TODO 实现之
}
}