本文中主要介绍一个集成了SpringDataJPA和FreeMarker的示例。
1、简单介绍
1.1 SpringDataJPA
1.1.1 jdbc介绍
一般来说,数据存储在关系型数据库,而java语言直接操作的是对象。Java访问数据库,一般来来说,可能需要写jdbc连接的代码。
JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。
我们安装好数据库之后,我们的应用程序也是不能直接使用数据库的,必须要通过相应的数据库驱动程序,通过驱动程序去和数据库打交道。其实也就是数据库厂商的JDBC接口实现,即对Connection等接口的实现类的jar文件。
image.png
从这个图中,可以看出jdbc也是一个非常优雅的设计实现,通过它,应用程序员无需关注不同数据库厂商的实现差异。
1.1.2 ORM介绍
JDBC连接相关的代码,对于应用程序原来说,还是比较繁琐的。这里出现了一列的ORM(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping,对象关系映射)框架:
- MyBatis:MyBatis 本是 Apache 的一个开源项目 iBatis,2010 年这个项目由 Apache Software Foundation 迁移到了 Google Code,并且改名为 MyBatis,其着力于 POJO 与 SQL 之间的映射关系,可以进行更为细致的 SQL,使用起来十分灵活、上手简单、容易掌握,所以深受开发者的喜欢,目前市场占有率最高,比较适合互联应用公司的 API 场景;缺点就是工作量比较大,需要各种配置文件的配置和 SQL 语句。
- Hibernate:Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库,并且对象有自己的生命周期,着力点对象与对象之间关系,有自己的 HQL 查询语言,所以数据库移植性很好。Hibernate 是完备的 ORM 框架,是符合 JPA 规范的,有自己的缓存机制,上手来说比较难,比较适合企业级的应用系统开发。
- Spring Data JPA:可以理解为 JPA 规范的再次封装抽象,底层还是使用了 Hibernate 的 JPA 技术实现,引用 JPQL(Java Persistence Query Language)查询语言,属于 Spring 的整个生态体系的一部分。由于 Spring Boot 和 Spring Cloud 在市场上的流行,Spring Data JPA 也逐渐进入大家的视野,他们有机的整体,使用起来比较方便,加快了开发的效率,使开发者不需要关系和配置更多的东西,完全可以沉浸在 Spring 的完整生态标准的实现下,上手简单、开发效率高,又对对象的支持比较好,又有很大的灵活性,市场的认可度越来越高。
- OpenJPA :是 Apache 组织提供的开源项目,它实现了 EJB 3.0 中的 JPA 标准,为开发者提供功能强大、使用简单的持久化数据管理框架,但功能、性能、普及性等方面更加需要加大力度,所以用的人不人不是特别多。
- QueryDSL:QueryDSL 可以在任何支持的 ORM 框架或者 SQL 平台上以一种通用的 API 方式来构建查询,目前 QueryDSL 支持的平台包括 JPA、JDO、SQL、Java Collections、RDF、Lucene、Hibernate Search,同时 Spring Data JPA 也对 QueryDSL 做了很好的支持。
1.1.3 JPA介绍
JPA(Java Persistence API)中文名 Java 持久层 API,是 JDK 5.0 注解或 XML 描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
Sun 引入新的 JPA ORM 规范出于两个原因:其一,简化现有 Java EE 和 Java SE 应用开发工作;其二,Sun 希望整合 ORM 技术,实现天下归一。
JPA 包括以下三方面的内容:
- 一套 API 标准,在 javax.persistence 的包下面,用来操作实体对象,执行 CRUD 操作,框架在后台替代我们完成所有的事情,开发者从繁琐的 JDBC 和 SQL 代码中解脱出来。
- 面向对象的查询语言:Java Persistence Query Language(JPQL),这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序的 SQL 语句紧密耦合。
- ORM(Object/Relational Metadata)元数据的映射,JPA 支持 XML 和 JDK 5.0 注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中。
JPA 的宗旨是为 POJO 提供持久化标准规范,由此可见,经过这几年的实践探索,能够脱离容器独立运行,方便开发和测试的理念已经深入人心了。Hibernate 3.2+、TopLink 10.1.3 以及 OpenJPA、QueryDSL 都提供了 JPA 的实现,以及最后的 Spring 的整合 Spring Data JPA。目前互联网公司和传统公司大量使用了 JPA 的开发标准规范。
image.png
1.2 FreeMarker
FreeMarker是一款模板引擎:即一种基于模板和要改变的数据,并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
模板编写为FreeMarker Template Language(FTL)。它是简单的,专用的语言, 不是像PHP那样成熟的编程语言。那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算,之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。
image.png
这种方式通常被称为MVC(模型视图控制器)模式,对于动态网页来说,是一种特别流行的模式。它帮助从开发人员(Java 程序员)中分离出网页设计师(HTML设计师)。设计师无需面对模板中的复杂逻辑,在没有程序员来修改或重新编译代码时,也可以修改页面的样式。
而FreeMarker最初的设计,是被用来在MVC模式的Web开发框架中生成HTML页面的,它没有被绑定到Servlet或HTML或任意Web相关的东西上。它也可以用于非Web应用环境中。
FreeMarker是免费的,基于Apache许可证2.0版本发布。
2、代码实现
2.1 创建基本的工程
pom.xml
文件,中引入了jpa、freemarker和springboot等相关的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lfqy.springboot</groupId>
<artifactId>FreemarkerTrial</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springBoot JPA的起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<!-- MySQL连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
</dependencies>
</project>
配置文件中配置了freemarker模板信息、数据库连接信息等,application.properties
:
spring.datasource.username=root
spring.datasource.password=lfqylfqy
spring.datasource.url=jdbc:mysql://localhost:3306/testdb?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#JPA Configuration:
spring.jpa.database=MySQL
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
#设定ftl文件路径
spring.freemarker.template-loader-path=classpath:/templates
写一个springboot的启动类com/lfqy/freemarker/SpringBootTrialApplication.java
:
package com.lfqy.freemarker;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootTrialApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootTrialApplication.class);
}
}
写一个测试的Controller类com/lfqy/freemarker/controller/TrialController.java
:
package com.lfqy.freemarker.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class TrialController {
@RequestMapping("/hello")
@ResponseBody
public String hello(){
return "springboot is ok!";
}
}
实际上,到这里,工程就可以启动了。访问/hello
返回springboot is ok!
。
2.2 数据库相关的内容
2.2.1 准备测试数据
mysql数据库中执行如下脚本,准备测试数据。创建一个user表,插入两条测试数据:
-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'zhangsan', '123', '张三');
INSERT INTO `user` VALUES ('2', 'lisi', '123', '李四');
COMMIT;
2.2.2 pojo、DAO和service
写一个pojo类com/lfqy/freemarker/pojo/User.java
:
package com.lfqy.freemarker.pojo;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
* Created by chengxia on 2022/4/14.
*/
@Entity
public class User {
// 主键
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 用户名
private String username;
// 密码
private String password;
// 姓名
private String name;
//setter和getter方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
DAO类直接继承的jpa的接口,com/lfqy/freemarker/dao/UserDao.java
:
package com.lfqy.freemarker.dao;
import com.lfqy.freemarker.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* Created by chengxia on 2022/4/14.
*/
public interface UserDao extends JpaRepository<User, Long> {
}
service的接口类,com/lfqy/freemarker/service/UserService.java
:
package com.lfqy.freemarker.service;
import com.lfqy.freemarker.pojo.User;
import java.util.Optional;
/**
* Created by chengxia on 2022/4/14.
*/
public interface UserService {
Optional<User> getUserById(long id);
}
service的接口实现类,com/lfqy/freemarker/service/impl/UserServiceImpl.java
:
package com.lfqy.freemarker.service.impl;
import com.lfqy.freemarker.dao.UserDao;
import com.lfqy.freemarker.pojo.User;
import com.lfqy.freemarker.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Optional;
/**
* Created by chengxia on 2022/4/14.
*/
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao;
@Override
public Optional<User> getUserById(long id) {
return userDao.findById(id);
}
}
在这个类中,直接调用了jpa的现成方法查询数据库。
JpaRepository查询方法解析流程说明:
Spring Data JPA框架在进行方法名解析时,会先把方法名多余的前缀截取掉
比如find、findBy、read、readBy、get、getBy,然后对剩下部分进行解析。这里的findById
就是根据ID属性进行查找的意思。如下列出了一些例子:
Keyword | Sample | JPQL snippet |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age ⇐ ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
TRUE | findByActiveTrue() | … where x.active = true |
FALSE | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
2.3 Controller和视图
写一个Controller,其中包含及直接将查询结果返回的方法,也包含一个将查询出的对象信息通过模板渲染成html例子的方法。
com/lfqy/freemarker/controller/UserController.java
:
package com.lfqy.freemarker.controller;
import com.lfqy.freemarker.pojo.User;
import com.lfqy.freemarker.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
/**
* Created by chengxia on 2022/4/14.
*/
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/user/{id}")
private User getUserById(@PathVariable long id) {
User user = userService.getUserById(id).get();
return user;
}
@RequestMapping("/show/{idStr}")
@ResponseBody
private ModelAndView showUserById(@PathVariable String idStr) {
long id = Long.parseLong(idStr);
User user = userService.getUserById(id).get();
ModelAndView mv = new ModelAndView();
mv.addObject("user",user);
mv.setViewName("userInfo");
return mv;
}
}
这里的showUserById
方法,返回的是一个名为userInfo
的模板,这个模板是一个以userInfo
命名的ftl文件。
templates/userInfo.ftl
:
<html>
<head>
<title>User Info</title>
</head>
<body>
用户列表:<br>
<table border="1">
<tr>
<th>id</th>
<th>username</th>
<th>password</th>
<th>name</th>
</tr>
<#--<#list userList as user>-->
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
<td>${user.name}</td>
</tr>
<#--</#list>-->
</table>
</body>
</html>
3、运行效果和提示
到这里,全部的编码就完成了。工程结构如下:
运行
com.lfqy.freemarker.SpringBootTrialApplication
,可以启动工程。访问
http://localhost:8080/hello
:image.png
访问http://localhost:8080/user/1
:
访问http://localhost:8080/show/1
:
这里在之前调试的时候,访问http://localhost:8080/show/1
,对应的Controller方法总会执行两遍,查了好久,发现是由于没有在pom文件中引入freemarker依赖,导致方法中返回的mv
不会被认为是一个模板,而是会把模板名放到参数的位置,重新执行一般Controller方法。添加freemarker的pom依赖后,问题得以正常解决。
4、freemarker模板的嵌套示例
新增一个测试模板嵌套的Controller,com/lfqy/freemarker/controller/FtlIncludeController.java
:
package com.lfqy.freemarker.controller;
import com.lfqy.freemarker.pojo.User;
import com.lfqy.freemarker.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
/**
* Created by chengxia on 2022/4/14.
*/
@RestController
public class FtlIncludeController {
@RequestMapping("/index")
private ModelAndView index() {
ModelAndView mv = new ModelAndView();
mv.setViewName("index");
return mv;
}
}
新增一个模板文件,其中嵌套了好多子模板。
templates/index.ftl
:
<html>
<head>
<title>FreeMarker include demo page</title>
<#include "pubjs.ftl">
<script>
function foo() {
alert('This is alert of function foo!');
}
<#include "jsfunc.ftl">
function jquerytest() {
//alert($('#testp'));
$('#testp').css("background","yellow");
}
</script>
</head>
<body>
<#include "top.ftl">
<br/>
<p id="testp">top middle line</p>
<br/>
<#include "top.ftl">
<br/>
<button onclick="foo()">ExecuteFoo</button>
<button onclick="bar()">ExecuteBar</button>
<button onclick="jquerytest()">jquerytest1</button>
<button onclick="$('#testp').css('background','blue');">jquerytest2</button>
<p>middle bottom line</p>
<br/>
<#include "bottom.ftl">
</body>
</html>
templates/top.ftl
:
<h2>This is top area.</h2>
templates/middle.ftl
:
<h2>This is middle area.</h2>
templates/bottomo.ftl
:
<h2>This is bottom area.</h2>
templates/jsfunc.ftl
:
function bar() {
alert('This is alert of function bar!');
}
templates/pubjs.ftl
:
<script src="jquery1.10.2.min.js"></script>
完成之后工程目录如下:
image.png
访问http://localhost:8080/index
:
点击页面按钮,运行都符合代码逻辑预期。
网友评论