QQ:61720061,邮箱:61720061@qq.com
项目说明:
前期自己做小项目用的是国产架构JFinal,整体使用感觉轻便、灵活、极简,特别是对数据库的操作更是节省了大量的精力。工作上大家都在用SpringBoot,数据库ORM用的则是MyBatisPlus,整体使用感觉在数据库操作方面极为不便,特别是xml文件配置成为了项目的难点之一。鉴于这样的情况,我就想为何不能把JFinal在数据ORM方面的能力搬到SpringBoot上(其实JFinal在Controller、Router方面也很有优势),即不用改变架构,也可以以极简的思路完成数据的操作。
这个整合项目在这样的情景下产生:
1、目标
(1)在SpringBoot项目中集成JFinal的ORM功能(实现)
(2)完成项目数据库的连接、代码书写,包括Controller、Service、Dao、View层(实现)
(3)完成SQL与代码分离编写和运维(实现)
2、准备
(1)SpringBoot项目,环境及jar版本号,JDK14.8+SpringBoot2.6.13+Mysql5.1.49
(2)JFinal版本5.2.2
(3)Mysql服务器版本5.5.6
3、测试准备
(1)新建数据库leave和表user,字段user_id、user_name、user_tel,其他字段自行设计
4、整体实施情况如下
(1)项目结构
src/main/java
├── com.example.demo
│ ├── config
│ │ ├── _Generator.java
│ │ └── ActiveRecordConfig.java
│ ├── controller
│ │ └── UserController.java
│ ├── model
│ │ ├── base
│ │ │ └── BaseUser.java
│ │ ├── _MappingKit.java
│ │ └── User.java
│ ├── service
│ │ └── UserService.java
│ └── DemoApplication.java
src/main/resources
├── application.properties
├──sql
│ ├── user.sql
├── templates
│ ├── userList.html
│ └── userView.html
说明:①这里的base子包与BaseUser.java可生成也可不生成,区别在于不生成BaseUser.java时,User.jave继承Mode<User>,生成时,User.java继承BaseUser<User>,而这两者区别在于后期前端取数据时的方法;当生成时,前端可以用user.userId取值,如果不生成时,前端只能采用user.get(‘user_id’)来取值。②_Generator.java用来自动批量生成User.java、BaseUser.java和_MappingKit.java文件,生成新的实体类前应先删除已有类或删除需要重新生成的类。③如果项目没有使用BaseUser.java的生成,那么数据表字段变化则不需要重新生成User.java,具有自适应性。
(2)创建项目
添加项目Maven依赖(pom.xml),除SpringBoot、lombok、devtools外,还应当添加thymeleaf、jfinal、mysql-connector-java、druid,另外我还引入了undertow,具体如下,
【xml】
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- JFinal -->
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>jfinal</artifactId>
<version>5.2.2</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- Druid 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<!-- Thymeleaf 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
------
(3)完成数据源配置 (application.properties)
【properties】
server.port=8080
server.servlet.context-path=/demo
spring.datasource.url=jdbc:mysql://localhost:3306/leave?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#thymeleaf
spring.thymeleaf.mode=HTML
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
------
(4)集成Jfinal
完成JFinal的ActiveRecord与SpringBoot的集成配置,配置文件命名为ActiveRecordConfig.java,代码如下:
【java】
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.wall.WallFilter;
import com.example.demo.model._MappingKit;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.druid.DruidPlugin;
import com.jfinal.template.Engine;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ActiveRecordConfig {
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
// 扫描 SQL 文件的目录路径
String directoryPath = System.getProperty("user.dir")+ "/src/main/resources/sql";
@Bean
public ActiveRecordPlugin initActiveRecordPlugin() {
DruidPlugin dp = new DruidPlugin(url, username, password);
WallFilter wallFilter = new WallFilter();// 加强数据库安全
wallFilter.setDbType("mysql");
dp.addFilter(wallFilter);
dp.addFilter(new StatFilter()); // 添加StatFilter才会有统计数据
dp.start();
ActiveRecordPlugin arp = new ActiveRecordPlugin(dp);
arp.setShowSql(true); //设置sql显示与调试模式打开
arp.setDevMode(true);
_MappingKit.mapping(arp);
// 扫描 SQL 文件
String[] paths=directoryPath.split("/");
String sqlFolder=paths[paths.length-1];
List<String> sqlFiles = SqlFileScanner.scanSqlFiles(directoryPath);
for (String sqlFile : sqlFiles) {
arp.addSqlTemplate(sqlFolder+"/"+sqlFile);
}
arp.start(); //与jfinal web环境唯一的不同是要手动调用一次相关插件的start()方法
return arp;
}
}
【SqlFileScanner.java】
//对sql文件夹进行扫描
public class SqlFileScanner {
public static List<String> scanSqlFiles(String directoryPath) {
List<String> sqlFiles = new ArrayList<>();
File directory = new File(directoryPath);
if (directory.exists() && directory.isDirectory()) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile() && file.getName().endsWith(".sql")) {
sqlFiles.add(file.getName());
}
}
}
}
return sqlFiles;
}
}
(5)创建实体类定义 (User.java)
【java】
package com.example.demo.model;
import com.jfinal.plugin.activerecord.Model;
//不生成BaseUser.java
public class User extends Model<User> {
public static final User dao = new User().dao();
}
//采用生成BaseUser.java
import com.jfinal.plugin.activerecord.Model;
Import com.example.demo.model.base.BaseUser;
public class User extends BaseUser<User> {
}
【BaseUser.java】
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/** Generated by JFinal, do not modify this file. */
@SuppressWarnings("serial")
public abstract class BaseUser<M extends BaseUser<M>> extends Model<M> implements IBean {
public void setUserId(java.lang.Long userId) { set("user_id", userId); }
public java.lang.Long getUserId() { return getLong("user_id"); }
public void setUserName(java.lang.String userName) { set("user_name", userName); }
public java.lang.String getUserName() { return getStr("user_name"); }
}
-------
(6)实体映射类 (_MappingKit.java)
【java】
package com.example.demo.model;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
public class _MappingKit {
public static void mapping(ActiveRecordPlugin arp) {
arp.addMapping("user", "user_id", User.class);
// 添加其他表的映射
}
}
------
(7)服务层实现 (UserService.java)
【java】
package com.example.demo.service;
import com.example.demo.model.User;
import com.jfinal.plugin.activerecord.Page;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private static final User dao =new User().dao();
public User findById(Integer id) {
return dao.findById(id);
}
public Page<User> paginate(int pageNumber, int pageSize) {
//第一种,将sql写在service代码层中,不利于 sql 优化及维护
//return dao.paginate(pageNumber, pageSize, "SELECT *", "FROM user ORDER BY user_id DESC");
//第二种,将sql写在sql模板中,方便 sql 优化及维护;这种写法需要自己探索复杂用法和参数传递方法
SqlPara sqlPara=dao.getSqlPara("user.paginate","");
return dao.paginate(pageNumber, pageSize, sqlPara);
}
public boolean save(User user) {
return user.save();
}
public boolean update(User user) {
return user.update();
}
public boolean delete(Integer id) {
return dao.deleteById(id);
}
public User findByUsername(String username) {
return dao.findFirst("SELECT * FROM user WHERE username = ?", username);
}
}
------
(8)控制器层实现 (UserController.java)
【java】
package com.example.demo.controller;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import com.jfinal.plugin.activerecord.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users")
public String listUsers(Model model, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) {
Page<User> userPage = userService.paginate(page, size);
model.addAttribute("userPage", userPage);
return "userList";
}
@GetMapping("/users/{id}")
public String viewUser(Model model, @PathVariable int id) {
User user = userService.findById(id);
model.addAttribute("user", user);
return "userView";
}
}
------
(9)主应用类 (DemoApplication.java)
【java】
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import lombok.extern.slf4j.Slf4j;
@SpringBootApplication
@Slf4j
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
Log.info(“项目启动成功”);
}
}
------
(10)视图层
用户列表页面 (templates/userList.html)
【html】
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>用户列表</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-4">
<h1 class="mb-4">用户列表</h1>
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>ID</th><th>用户名</th><th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${userPage.list}">
<!--后端如果生成了BaseUser.java,就可以用user.userId取值,且符合驼峰命名 -->
<td th:text="${user.userId}" ></td>
<!--后端如果没有生成BaseUser.java,就可以用user.get('user_name')取值,与数据库字段名相同 -->
<td th:text="${user.get('user_name')}"></td>
<td>
<a th:href="@{/users/{id}(id=${user.get(‘user_id’)})}" class="btn btn-primary btn-sm">查看</a>
</td>
</tr>
</tbody>
</table>
<!-- 分页控件 -->
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center mt-3">
<li class="page-item" th:if="${userPage.pageNumber > 1}">
<a class="page-link" th:href="@{/users(page=${userPage.pageNumber - 1}, size=${userPage.pageSize})}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li class="page-item active">
<a class="page-link" th:text="${userPage.pageNumber}" aria-current="page"></a>
</li>
<li class="page-item" th:if="${userPage.pageNumber < userPage.totalPage}">
<a class="page-link" th:href="@{/users(page=${userPage.pageNumber + 1}, size=${userPage.pageSize})}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</div>
</body>
</html>
------
用户详情页面 (templates/userView.html)
【html】
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>用户详情</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-4">
<h1 class="mb-4">用户详情</h1>
<div class="card">
<div class="card-body">
<h5 class="card-title">用户信息</h5>
<ul class="list-group">
<li class="list-group-item"><strong>ID:</strong> <span th:text="${user.get(‘user_id’)}"></span></li>
<li class="list-group-item"><strong>用户名:</strong> <span th:text="${user.get(‘user_name’)}"></span></li>
<!-- 如果有更多字段,可以在这里添加 -->
</ul>
<a href="/users" class="btn btn-secondary mt-3">返回用户列表</a>
</div>
</div>
</div>
</body>
</html>
------
(11)数据库创建sql
【sql】
CREATE DATABASE IF NOT EXISTS `leave` ;
USE `leave`;
DROP TABLE IF EXISTS `leaves`;
CREATE TABLE `leaves` (
`leave_id` bigint(11) NOT NULL AUTO_INCREMENT,
`leave_user` char(20) DEFAULT NULL,
`leave_title` char(100) DEFAULT NULL,
`leave_note` text,
`leave_time` char(20) DEFAULT NULL,
`leave_show` char(1) DEFAULT '0',
`leave_bak` text,
`leave_bak_time` char(20) DEFAULT NULL,
`leave_bak_user` char(20) DEFAULT NULL,
PRIMARY KEY (`leave_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`user_id` bigint(11) NOT NULL AUTO_INCREMENT,
`user_name` char(20) NOT NULL,
`user_pwd` char(50) NOT NULL DEFAULT '123456',
`user_tel` char(20) DEFAULT NULL,
`user_last_time` char(30) DEFAULT NULL,
`user_right` char(20) DEFAULT 'user' COMMENT 'user,admin',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
完成,有问题欢迎交流。