Java 小记 — Spring Boot 的实践与思考
前言
本篇随笔用于记录我在学习 Java 和构建 Spring Boot 项目过程中的一些思考,包含架构、组件和部署方式等。下文仅为概要,待闲时逐一整理为详细文档。

1. 组件
开源社区如火如荼,若在当下我们还要去重复 “造轮子” 那真是罪过罪过(当然也并不意味着所有的一切都可拿来即用,了解他,使用他,若有能力,去完善它)。因此,当我们拿到需求的时候首先应当进行拆解,哪些模块在社区中已有比较成熟的解决方案,然后大致罗列一个粗略的所需组件列表(后续根据架构的设计和兼容情况再进行调整)。
1.1 ORM
用于解耦实体对象的装载过程,他让我们的编程过程更关注业务逻辑本身,其重要性毋庸多言。在 Spring Boot 中比较主流的 ORM 框架有 Spring-Data-JPA 和 MyBatis。
JPA 规范的好处是我们几乎完全专注于业务逻辑本身,JpaRepository 中的接口能够满足大部分简单的操作逻辑了,若要扩展,我们也可以再抽离出一个父类,添加自定义的通用 CRUD 操作,如此一来仓储层的代码变得异常优雅和简洁。以下是一个简单的实现案例:
application.yml:
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://mysql:3306/youclk?characterEncoding=utf8&useSSL=false
username: root
password: youclk
jpa:
hibernate:
ddl-auto: create
show-sql: true
BookRespository.java:
public interface BookRespository extends JpaRepository<Book,Integer> {
}
Book.java:
@Data
public class Book {
private Long id;
private String name;
private Date createTime;
}
BookRespositoryTest.java:
@RunWith(SpringRunner.class)
@SpringBootTest
public class BookRespositoryTest {
@Autowired
private BookRespository repository;
@Test
public void saveTest() {
Assert.assertEquals(repository.findAll().size(),0);
}
}
映射得到的 sql 如下:
Hibernate: select book0_.id as id1_0_, book0_.name as name2_0_, book0_.number as number3_0_ from book book0_
如果是几年前的话我肯定会首选 JPA, 但是期间近两年的 EFCore 开发经历让我的选择变得谨慎。C# 是 Lambda 和 Linq 的先驱者,因此 .NET + EF 实践 Code First 着实优雅。然而在迁移 EFCore 的过程中遇到的问题真是不少,比如说 EFCore 1.x 的时候处理 GroupBy 是全表扫描然后拿到内存中过滤。对于旧项目的迁移我们一般没有精力去验证 ORM 映射生成的每条 SQL 语句,而且本地环境因数据基数少,测试阶段很难直观地体现出来,但部署后就悲剧了,服务和数据库一起都要死要死的。
由此引发的思考是当进行里程碑版本的升级和迁移的时候,新版本 ORM 框架所生成的 SQL 还能否完全正确体现之前代码中的逻辑。
在 .NET Core 中除了 EFCore 还有一个非常优秀的 ORM 框架是 Dapper,这个和 MyBatis 非常像,相当于半自动档吧,开发者能更好地掌控 SQL,但牺牲了一定的简洁。介于曾经的入坑经历,至少在查询方面我选择 MyBatis,实例如下:
BookRespository.java:
public interface BookRepository {
List<Book> findAll();
@Select("SELECT name FROM book WHERE id = #{id}")
@Results({
@Result(property = "name", column = "name")
})
String findBookName(Long id);
}
BookRepository.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.youclk.demo.repositories.BookRepository" >
<resultMap id="BaseResultMap" type="com.youclk.demo.domain.Book" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="createTime" property="createTime" jdbcType="DATE" />
</resultMap>
<sql id="Base_Column_List" >
id, name, createTime
</sql>
<select id="findAll" resultMap="BaseResultMap" >
SELECT
<include refid="Base_Column_List" />
FROM book
</select>
</mapper>
Application.java 中加一行 MapperScan 注解:
@SpringBootApplication()
@MapperScan("com.youclk.demo.repositories")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
以上 BookRepository.java 中分别使用了注解和 xml,虽然在 Spring 中注解是王道,但我还是喜欢 xml 这种方式。一方面接口上写这么一堆 SQL 和返回类型看起来很难受,头重脚轻,而且没有突出重点,另一方面在于 IDEA 中对 xml 的智能提示相当友好,效率上也不见得是写注解快多少。不过采用注解方式少了些配置文件,项目结构更优。
1.2 日志
Java 中主流的日志框架有 JUL(java.util.logging)、Log4j、Log4j2 和 Logback 这四款,JUL 因过于简陋优先淘汰,剩下的三款都是同一个作者开发,Log4j 太旧速度慢,Log4j2 太新问题多,因此 Logback 就是最优解,对应的接口门面我选择 SLF4j。需要导入的包有: slf4j-api、ogback-classic 和 logback-core,以下是我的案例:
logback-spring.xml:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE_WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/com.youclk.demo/warm/%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>90</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/com.youclk.demo/error/%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>90</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="warn">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE_WARN"/>
<appender-ref ref="FILE_ERROR"/>
</root>
</configuration>
LoggerTest.java:
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class LoggerTest {
@Test
public void testOutput() {
log.error("error");
log.warn("warn");
log.info("info");
}
}
以上将日志的输出整理归类,若要分布式部署,则只要每个 Container 都挂载 log 目录便可做日志的集中管理。Logback 更详细信息可查阅 “官方文档”
1.3 缓存
Memcached 和 Redis 都老生常谈了,Redis 支持更多的数据结构和操作,并且二者性能差距不大,因此选他无疑,实现上也极其简单,如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testSetCache() {
ValueOperations<String, Book> operations = redisTemplate.opsForValue();
operations.set("my-book",new Book(),5, TimeUnit.SECONDS);
System.out.println(operations.get("my-book"));
}
}
另外还有一种注解的用法功能上要强大不少:
@Service
@CacheConfig(cacheNames="book")
public class BookService {
@Autowired
private BookRepository bookRepository;
@Cacheable()
public List<Book> findAll() {
return bookRepository.findAll();
}
}
2. 架构
程序员界一直存在着一条所谓的 “语言鄙视链”,曾经为了 “打嘴炮” 而粗略地对比过 Java 和 C#,由于未深入探究,因此我一直以来的观念都是 C# 的语法糖比 Java 优雅太多。直到我切身感受了使用 Java 构建项目,或许就原生的二者来说确实是 C# 更优雅,但加上社区的力量可就不好说了。比如习惯了 C# 自动属性的我最不喜欢的就是 Java 那么一堆冗长的 get 和 set, 直到我认识了 lombok,简直汗颜啊,源码注解原来还能这么灵活地使用,由此展开只要你足够有耐心,想要什么语法糖自定义注解去实现就好。还有其他的零零总总, Java 中注解和 AOP 范式的成熟应用拓展了更多的 “编程姿势” 呵(C# 中通过反射也能做到,奈何封闭的生态导致为其造轮子的不多,现今微软已拥抱开源,给个祝福吧,互相竞争才更有意思嘛)。
回归本节主题,在 .NET 中 DDD 架构的应用可谓是相当普及,你要是不晓得领域、充血模型、事件驱动等概念都不好意思说熟悉 .NET。
这里简要概括一下,顺便谈谈我的想法,传统 DDD 架构主要分四层,分别为:User Interface(用户界面层)、Application(应用层)、Domain(领域层) 和 Infrastructure(基础设施层)。界面层就不说了,应用层主要起协调作用,比如一个请求从用户界面层过来,应用层应当分析其需要哪几个领域模块参与,并协调他们工作,但其本身不应包含任何的业务规则,基础设施层在实际应用中最重要的功能就是提供数据持久化机制。领域层则为整个项目的核心,其应囊括几乎全部的业务规则,我们应当在该层根据项目需求设计领域模型,抽离出领域服务,每个领域模块应当专注于处理其自身的核心业务逻辑,非核心的业务可封装为领域事件交由异步队列处理。其次,领域层作为核心,他不应该对其他层有所依赖,因此一般他会包含基础设施层的实现接口。
之前对于领域模块中的通用逻辑或非核心业务,我通常的处理方案是封装为领域事件分发,现在想想如此做法不合理之处,领域事件有些被滥用了。介于 AOP 在 Spring Boot 的广泛应用,领域模型中除了领域实体、值对象、领域服务、领域事件和工作单元之外再加一个领域切面也是极好的。另外,对于领域实体最后的持久化操作如果使用 MyBatis 此类的 ORM 框架那整个编程过程就变得相当繁琐,在领域中比较容易做到的是对实体状态的跟踪,因此持久化选择 JPA 规范的 ORM 框架才更为合理,但在查询上我更喜欢 MyBatis,因此若要做读写分离的话, JPA 和 MyBatis 分别对应主备数据库操作正好。
3. 部署
自从习惯了 Docker 之后,我已经不适应服务的单独部署了,具体操作详见我的这篇博文:“Compose & Swarm”。
结语
近期正在寻觅 “全栈” 或 “Java 开发” 的工作岗位,若有意向,欢迎留言,微信: youclk。
我的公众号《有刻》,我们共同成长!

Java 小记 — Spring Boot 的实践与思考的更多相关文章
- Java 小记 — Spring Boot 注解
前言 本篇随笔将对 Spring Boot 中的常用注解做一个简单的整理归档,写作顺序将从启动类开始并逐步向内外扩展,目的即为了分享也为了方便自己日后的回顾与查阅. 1. Application 启动 ...
- Spring Boot 2 实践记录之 封装依赖及尽可能不创建静态方法以避免在 Service 和 Controller 的单元测试中使用 Powermock
在前面的文章中(Spring Boot 2 实践记录之 Powermock 和 SpringBootTest)提到了使用 Powermock 结合 SpringBootTest.WebMvcTest ...
- Spring Boot 2 实践记录之 使用 ConfigurationProperties 注解将配置属性匹配至配置类的属性
在 Spring Boot 2 实践记录之 条件装配 一文中,曾经使用 Condition 类的 ConditionContext 参数获取了配置文件中的配置属性.但那是因为 Spring 提供了将上 ...
- Spring Boot 2 实践记录之 MyBatis 集成的启动时警告信息问题
按笔者 Spring Boot 2 实践记录之 MySQL + MyBatis 配置 中的方式,如果想正确运行,需要在 Mapper 类上添加 @Mapper 注解. 但是加入此注解之后,启动时会出现 ...
- 框架-Java:Spring Boot
ylbtech-框架-Java:Spring Boot 1.返回顶部 1. Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该 ...
- Spring Boot 2 实践记录之 Redis 及 Session Redis 配置
先说 Redis 的配置,在一些网上资料中,Spring Boot 的 Redis 除了添加依赖外,还要使用 XML 或 Java 配置文件做些配置,不过经过实践并不需要. 先在 pom 文件中添加 ...
- Java框架Spring Boot & 服务治理框架Dubbo & 应用容器引擎Docker 实现微服务发布
微服务系统架构实践 开发语言Java 8 框架使用Spring boot 服务治理框架Dubbo 容器部署Docker 持续集成Gitlab CI 持续部署Piplin 注册中心Zookeeper 服 ...
- Mysql事务开启方式(客户端+java手动+Spring Boot)
一:概念 作为单个逻辑单元执行一系列操作,要么完全执行,要么完全不执行.举例 我们需要向数据库插入3条数据(我们希望这三条数据要么全部插入成功,要么全部失败), 比如第一条数据插入成功,插入第二条数据 ...
- Java框架spring Boot学习笔记(三):Controller的使用
Controller注解介绍 @Controller:处理http请求 @RestController: Spirng4之后新加的注解,其实是一个组合注解等同于@ResponseBody和@Contr ...
随机推荐
- Linux目录结构及作用
/:根目录 /bin:存放基础系统所需的最基础的命令(程序) binary 比如:ls.cp.mkdir等 功能和/usr/bin类似,这个目录中的文件都是可执行的,普通用户都可以使用的命令 /b ...
- Appium基于Python unittest自动化测试 & 自动化测试框架 -- PO并生成html测试报告
基于python单元测试框架unittest完成appium自动化测试,生成基于html可视化测试报告 代码示例: #利用unittest并生成测试报告 class Appium_test(unitt ...
- java多线程编程——锁优化
并发环境下进行编程时,需要使用锁机制来同步多线程间的操作,保证共享资源的互斥访问.加锁会带来性能上的损坏,似乎是众所周知的事情.然而,加锁本身不会带来多少的性能消耗,性能主要是在线程的获取锁的过程.如 ...
- Python 判断闰年,判断日期是当前年的第几天
http://www.cnblogs.com/vamei/archive/2012/07/19/2600135.html Python小题目 针对快速教程 作业答案 写一个程序,判断2008年是否是闰 ...
- PHP对象和接口抽象类注意事项
Php 的对象的实现: 注:对象名称最好以对象名.class.php来进行命名. 1. 对于静态类的成员不能使用$this->这个来调用其静态类的成员和函数,应该使用self::成员或者方法来进 ...
- try{}里有一个 return 语句,那么紧跟在这个 try 后的 finally {}里的 code 会 不会被执行,什么时候被执行,在 return 前还是后?
这是一道面试题,首先finally{}里面的code肯定是会执行的,至于在return前还是后, 看答案说的是在return后执行,我觉得不对,百度了一下,有说return前的,有说return后的, ...
- Qt 网络模块如何使用?
1.网络模块介绍 类名 说明 中文 QAbstractNetworkCache The interface for cache implementations 缓存实现的接口 QNetworkCach ...
- asp.net程序发布详解
本文转载自Alan_beijing的博客ASP.NET 程序发布详细过程.内容进行了部分更改. ASP.NET网站的发布,无论是初学者还是高手,在程序的发布过程中或多或少会存在一些问题,譬如VS发布A ...
- 从 PHP 到 Java
* { color: #3e3e3e } body { font-family: "Helvetica Neue", Helvetica, "Hiragino Sans ...
- hadoop配置遇到问题的解决
1. ssh localhost: 不能登陆:将错误提示中的文件全部删除.原因:登陆过远程主机 2. 问题: 伪分布式datanode启动不了:在datanode的log日志文件出现以下错误提示: ...