Spring Boot中集成SpringDataJPA和FreeMarker的示例

本文中主要介绍一个集成了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等相关的依赖:



    4.0.0

    com.lfqy.springboot
    FreemarkerTrial
    1.0-SNAPSHOT
    
        org.springframework.boot
        spring-boot-starter-parent
        2.0.2.RELEASE
    
    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.springframework.boot
            spring-boot-starter-data-jpa
            2.2.6.RELEASE
        
        
        
            mysql
            mysql-connector-java
        
        
            org.springframework.boot
            spring-boot-starter-freemarker
            2.1.1.RELEASE
        
    

配置文件中配置了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 {
}

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 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 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



    User Info


用户列表:
<#--<#list userList as user>--> <#---->
id username password name
${user.id} ${user.username} ${user.password} ${user.name}

3、运行效果和提示

到这里,全部的编码就完成了。工程结构如下:

image.png

运行com.lfqy.freemarker.SpringBootTrialApplication,可以启动工程。
访问http://localhost:8080/hello
image.png

访问http://localhost:8080/user/1

image.png

访问http://localhost:8080/show/1

image.png

这里在之前调试的时候,访问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



    FreeMarker include demo page
    <#include "pubjs.ftl">
    


<#include "top.ftl">

top middle line


<#include "top.ftl">

middle bottom line


<#include "bottom.ftl">

templates/top.ftl

This is top area.

templates/middle.ftl

This is middle area.

templates/bottomo.ftl

This is bottom area.

templates/jsfunc.ftl

function bar() {
alert('This is alert of function bar!');
}

templates/pubjs.ftl


完成之后工程目录如下:


image.png

访问http://localhost:8080/index

image.png

点击页面按钮,运行都符合代码逻辑预期。

参考资料

  • JDBC详解
  • 第01课:整体认识 JPA
  • SpringDataJpa:JpaRepository增删改查
  • springboot入门
  • Freemarker的FTL指令之include和if指令

你可能感兴趣的:(Spring Boot中集成SpringDataJPA和FreeMarker的示例)