基於spring-boot&spring-data-jpa的web開發環境整合

gongxufan發表於2018-06-21

新技術?

spring-boot並不是全新的技術棧,而是整合了spring的很多元件,並且以約定優先的原則進行組合。使用boot我們不需要對冗雜的配置檔案進行管理,主需要用它的註解便可啟用大部分web開發中所需要的功能。本篇就是基於boot來配置jpa和靜態檔案訪問,進行web應用的開發。

模板or靜態頁面

最原始的jsp頁面在springboot中已經不在預設支援,spring-boot預設使用thymeleaf最為模板。當然我們也可以使用freemark或者velocity等其他後端模板。但是按照前後端分離的良好設計,我們最好採用靜態頁面作為前端模板,這樣前後端完全分離,把資料處理邏輯寫在程式並提供介面供前端呼叫。這樣的設計更加靈活和清晰。

專案搭建

我們將討論專案的結構、application配置檔案、靜態頁面處理、自定義filter,listener,servlet以及攔截器的使用。最後集中討論jpa的配置和操作以及如何進行單元測試和打包部署。

專案結構

專案使用maven進行依賴管理和構建,整體結構如下圖所示:
img
我們的HTML頁面和資原始檔都在resources/static下,打成jar包的時候static目錄位於/BOOT-INF/classes/。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
複製程式碼
<?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>gxf.dev</groupId>
    <artifactId>topology</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.tomcat</groupId>
                    <artifactId>tomcat-jdbc</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

      <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
        </dependency>

        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.3.10.RELEASE</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <id>nexus-aliyun</id>
            <name>Nexus aliyun</name>
            <layout>default</layout>
            <url>http://maven.aliyun.com/nexus/content/groups/public</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>gxf.dev.topology.Application</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
複製程式碼

spring-boot-starter-parent使我們專案的父pom。
spring-boot-starter-web提供嵌入式tomcat容器,從而使專案可以通過打成jar包的方式直接執行。
spring-boot-starter-data-jpa引入了jpa的支援。
spring-boot-test和junit配合做單元測試。
mysql-connector-java和HikariCP做資料庫的連線池的操作。
spring-boot-maven-plugin外掛能把專案和依賴的jar包以及資原始檔和頁面一起打成一個可執行的jar(執行在內嵌的tomcat)

啟動人口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
複製程式碼
package gxf.dev.topology;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@EnableAutoConfiguration
@ServletComponentScan
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}
複製程式碼

這裡ServletComponentScan註解是啟用servlet3的servler和filter以及listener的支援,下面會提到該用法。要注意的是:不能引入@EnableWebMvc註解,否則需要重新配置檢視和資原始檔對映。這樣就不符合我們的前後端分離的初衷了。

靜態資源處理

spring-boot預設會去classpath下面的/static/,/public/ ,/resources/目錄去讀取靜態資源。因此按照約定優先的原則,我們直接把我們應用的頁面和資原始檔直接放在/static下面,如下圖所示:
img
這樣我們訪問系統主頁就會自動載入index.html,而且它所引用的資原始檔也會在static/下開始載入。

application.yml

我們在application配置檔案中設定各種引數,它可以是傳統的properties檔案也可以使用yml來逐級配置。本文采用的第二種方式yml,如果不懂可以參考:baike.baidu.com/item/YAML/1…。其內容如下:

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
35
36
37
38
39
40
41
複製程式碼
server:
    port: 8080
    context-path: /topology
    session:
      timeout: 30
    tomcat:
      uri-encoding: utf-8

logging:
    level:
        root: info
        gxf.dev.topology: debug
        #當配置了loggin.path屬性時,將在該路徑下生成spring.log檔案,即:此時使用預設的日誌檔名spring.log
        #當配置了loggin.file屬性時,將在指定路徑下生成指定名稱的日誌檔案。預設為專案相對路徑,可以為logging.file指定絕對路徑。
        #path: /home/gongxufan/logs
    file: topology.log

spring:
    jpa:
      show-sql: true
      open-in-view: false
      hibernate:
        naming:
          #配置ddl建表欄位和實體欄位一致
          physical-strategy: gxf.dev.topology.config.RealNamingStrategyImpl
          ddl-auto: update
      properties:
        hibernate:
          format_sql: true
          show_sql: true
          dialect: org.hibernate.dialect.MySQL5Dialect
    datasource:
        url: jdbc:mysql://localhost:3306/topology
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: qwe
        hikari:
              cachePrepStmts: true
              prepStmtCacheSize: 250
              prepStmtCacheSqlLimit: 2048
              useServerPrepStmts: true
複製程式碼

使用idea開發工具在編輯器會有自動變數提示,這樣非常方便進行引數的選擇和查閱。

server

server節點可以配置容器的很多引數,比如:埠,訪問路徑、還有tomcat本身的一些引數。這裡設定了session的超時以及編碼等。

logging

日誌級別可以定義到具體的哪個包路徑,日誌檔案的配置要注意:path和file配置一個就行,file預設會在程式工作目錄下生成,也可以置頂絕對路徑進行指定。

datasource

這裡使用號稱效能最牛逼的連線池hikaricp,具體配置可以參閱其官網:brettwooldridge.github.io/HikariCP/

jpa

這裡主要注意下strategy的配置,關係到自動建表時的欄位命名規則。預設會生成帶_劃線分割entity的欄位名(駱駝峰式)。

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
複製程式碼
package gxf.dev.topology.config;

/**
 * ddl-auto選項開啟的時候生成表的欄位命名策略,預設會按照駱駝峰式風格用_隔開每個單詞
 * 這個類可以保證entity定義的欄位名和資料表的欄位一致
 * @auth gongxufan
 * @Date 2016/8/3
 **/

import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;

import java.io.Serializable;


public class RealNamingStrategyImpl extends org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy implements Serializable {

    public static final PhysicalNamingStrategyStandardImpl INSTANCE = new PhysicalNamingStrategyStandardImpl();

    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
        return new Identifier(name.getText(), name.isQuoted());
    }

    @Override
    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
        return new Identifier(name.getText(), name.isQuoted());
    }

}
複製程式碼

註冊web元件

1) 最新的spring-boot引入新的註解ServletComponentScan,使用它可以方便的配置Servlet3+的web元件。主要有下面這三個註解:

1
2
3
複製程式碼
@WebServlet
@WebFilter
@WebListener
複製程式碼

只要把這些註解標記元件即可完成註冊。

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
35
36
複製程式碼
package gxf.dev.topology.filter;

import org.springframework.core.annotation.Order;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * author:gongxufan
 * date:11/14/17
 **/
@Order(1)
@WebFilter(filterName = "loginFilter", urlPatterns = "/login")
public class LoginFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("login rquest");
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {

    }
}
複製程式碼

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
35
36
37
38
39
40
複製程式碼
package gxf.dev.topology.filter;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/**
 * 自定義listener
 * Created by gongxufan on 2016/7/5.
 */
@WebListener
public class SessionListener implements HttpSessionListener,HttpSessionAttributeListener {

    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        System.out.println("init");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        System.out.println("destroy");
    }

    @Override
    public void attributeAdded(HttpSessionBindingEvent se) {
        System.out.println(se.getName() + ":" + se.getValue());
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent se) {

    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent se) {

    }
}
複製程式碼

2) 攔截器的使用
攔截器不是Servlet規範的標準元件,它跟上面的三個元件不在一個處理鏈上。攔截器是spring使用AOP實現的,對controller執行前後可以進行干預,直接結束請求處理。而且攔截器只能對流經dispatcherServlet處理的請求才生效,靜態資源就不會被攔截。
下面頂一個攔截器:

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
複製程式碼
package gxf.dev.topology.filter;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * author:gongxufan
 * date:11/14/17
 **/
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        System.out.println("LoginInterceptor.preHandle()在請求處理之前進行呼叫(Controller方法呼叫之前)");
        // 只有返回true才會繼續向下執行,返回false取消當前請求
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        System.out.println("LoginInterceptor.postHandle()請求處理之後進行呼叫,但是在檢視被渲染之前(Controller方法呼叫之後)");
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        System.out.println("LoginInterceptor.afterCompletion()在整個請求結束之後被呼叫,也就是在DispatcherServlet 渲染了對應的檢視之後執行(主要是用於進行資源清理工作)");
    }
}
複製程式碼

要想它生效則需要加入攔截器棧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
複製程式碼
package gxf.dev.topology.config;

import gxf.dev.topology.filter.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * author:gongxufan
 * date:11/14/17
 **/
@Configuration
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    	//在這可以配置controller的訪問路徑
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}
複製程式碼

jpa操作

spring-boot已經整合了JPA的Repository封裝,基於註解的事務處理等,我們只要按照常規的JPA使用方法即可。以Node表的操作為例:

  1. 定義entity
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    複製程式碼
    package gxf.dev.topology.entity;
    
    import com.fasterxml.jackson.annotation.JsonInclude;
    
    import javax.persistence.Entity;
    import javax.persistence.Id;
    import javax.persistence.Table;
    import java.io.Serializable;
    
    /**
     * Created by gongxufan on 2014/11/20.
     */
    @Entity
    @Table(name = "node")
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public class Node implements Serializable {
    
        @Id
        private String id;
        private String elementType;
        private String x;
        private String y;
        private String width;
        private String height;
        private String alpha;
        private String rotate;
        private String scaleX;
        private String scaleY;
        private String strokeColor;
        private String fillColor;
        private String shadowColor;
        private String shadowOffsetX;
        private String shadowOffsetY;
        private String zIndex;
        private String text;
        private String font;
        private String fontColor;
        private String textPosition;
        private String textOffsetX;
        private String textOffsetY;
        private String borderRadius;
        private String deviceId;
        private String dataType;
        private String borderColor;
        private String offsetGap;
        private String childNodes;
        private String nodeImage;
        private String templateId;
        private String deviceA;
        private String deviceZ;
        private String lineType;
        private String direction;
        private String vmInstanceId;
        private String displayName;
        private String vmid;
        private String topoLevel;
        private String parentLevel;
        private Setring nextLevel;
        //getter&setter
    }
    複製程式碼

JsonInclude註解用於返回JOSN字串是忽略為空的欄位。

  1. 編寫repository介面

    1
    2
    3
    4
    5
    6
    7
    複製程式碼
    package gxf.dev.topology.repository;
    
    import gxf.dev.topology.entity.Node;
    import org.springframework.data.repository.PagingAndSortingRepository;
    
    public interface NodeRepository extends PagingAndSortingRepository<Node, String> {
    }
    複製程式碼
  2. 編寫Service

    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
    35
    36
    複製程式碼
    package gxf.dev.topology.service;
    
    import gxf.dev.topology.entity.Node;
    import gxf.dev.topology.repository.NodeRepository;
    import gxf.dev.topology.repository.SceneRepository;
    import gxf.dev.topology.repository.StageRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.annotation.Transactional;
    
    /**
     * dao操作
     * author:gongxufan
     * date:11/13/17
     **/
    @Component
    public class TopologyService {
    
        @Autowired
        private NodeRepository nodeRepository;
    
        @Autowired
        private SceneRepository sceneRepository;
    
        @Autowired
        private StageRepository stageRepository;
    
        @Transactional
        public Node saveNode(Node node) {
            return nodeRepository.save(node);
        }
    
        public Iterable<Node> getAll() {
            return nodeRepository.findAll();
        }
    }
    複製程式碼

單元測試

單元測試使用spring-boot-test和junit進行,需要用到下面的幾個註解:

1
2
複製程式碼
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
複製程式碼

測試程式碼如下:

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
35
36
複製程式碼
import gxf.dev.topology.Application;
import gxf.dev.topology.entity.Node;
import gxf.dev.topology.repository.CustomSqlDao;
import gxf.dev.topology.service.TopologyService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * author:gongxufan
 * date:11/13/17
 **/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ServiceTest {
    @Autowired
    private TopologyService topologyService;

    @Autowired
    private CustomSqlDao customSqlDao;
    @Test
    public void testNode() {
        Node node = new Node();
        node.setId("node:2");
        node.setDisplayName("test1");
        topologyService.saveNode(node);
    }

    @Test
    public void testNative(){
        System.out.println(customSqlDao.querySqlObjects("select * from node"));
        System.out.println(customSqlDao.getMaxColumn("id","node"));
    }
}
複製程式碼

jpa補充

使用JPA進行單表操作確實很方便,但是對於多表連線的複雜查詢可能不太方便。一般有兩種方式彌補這個不足:

  1. 一個是在Query裡標註為NativeQuery,直接使用原生SQL。不過這種方式在動態引數查詢到額情況下很不方便,這時候我們需要按條件拼接SQL。
  2. 自定義DAO
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    複製程式碼
    package gxf.dev.topology.repository;
    
    import com.mysql.jdbc.StringUtils;
    import org.hibernate.SQLQuery;
    import org.hibernate.criterion.CriteriaSpecification;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.Query;
    import java.math.BigDecimal;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * 支援自定義SQL查詢
     * Created by gongxufan on 2016/3/17.
     */
    @Component
    public class CustomSqlDao {
        @Autowired
        private EntityManagerFactory entityManagerFactory;
    
        public int getMaxColumn(final String filedName, final String tableName) {
            String sql = "select nvl(max(" + filedName + "), 0)  as max_num from " + tableName;
            Map map =  entityManagerFactory.getProperties();
            String dialect = (String) map.get("hibernate.dialect");
            //determine which database use
            if(!StringUtils.isNullOrEmpty(dialect)){
                if(dialect.contains("MySQL")){
                    sql = "select ifnull(max(" + filedName + "), 0)  as max_num from " + tableName;
                }
                if(dialect.contains("Oracle")){
                    sql = "select nvl(max(" + filedName + "), 0)  as max_num from " + tableName;
                }
            }
            int maxID = 0;
            List<Map<String, Object>> list = this.querySqlObjects(sql);
            if (list.size() > 0) {
                Object maxNum = list.get(0).get("max_num");
                if(maxNum instanceof Number)
                    maxID = ((Number)maxNum).intValue();
                if(maxNum instanceof String)
                    maxID = Integer.valueOf((String)maxNum);
            }
            return maxID + 1;
        }
    
        public List<Map<String, Object>> querySqlObjects(String sql, Integer currentPage, Integer rowsInPage) {
            return this.querySqlObjects(sql, null, currentPage, rowsInPage);
        }
    
        public List<Map<String, Object>> querySqlObjects(String sql) {
            return this.querySqlObjects(sql, null, null, null);
        }
    
        public List<Map<String, Object>> querySqlObjects(String sql, Map params) {
            return this.querySqlObjects(sql, params, null, null);
        }
    
        @SuppressWarnings("unchecked")
        public List<Map<String, Object>> querySqlObjects(String sql, Object params, Integer currentPage, Integer rowsInPage) {
            EntityManager entityManager = entityManagerFactory.createEntityManager();
            Query qry = entityManager.createNativeQuery(sql);
            SQLQuery s = qry.unwrap(SQLQuery.class);
    
            //設定引數
            if (params != null) {
                if (params instanceof List) {
                    List<Object> paramList = (List<Object>) params;
                    for (int i = 0, size = paramList.size(); i < size; i++) {
                        qry.setParameter(i + 1, paramList.get(i));
                    }
                } else if (params instanceof Map) {
                    Map<String, Object> paramMap = (Map<String, Object>) params;
                    Object o = null;
                    for (String key : paramMap.keySet()) {
                        o = paramMap.get(key);
                        if (o != null)
                            qry.setParameter(key, o);
                    }
                }
            }
    
            if (currentPage != null && rowsInPage != null) {//判斷是否有分頁
                // 起始物件位置
                qry.setFirstResult(rowsInPage * (currentPage - 1));
                // 查詢物件個數
                qry.setMaxResults(rowsInPage);
            }
            s.setResultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP);
            List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();
            try {
                List list = qry.getResultList();
                resultList = s.list();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                entityManager.close();
            }
            return resultList;
        }
    
    
        public int getCount(String sql) {
            String sqlCount = "select count(0) as count_num from " + sql;
            List<Map<String, Object>> list = this.querySqlObjects(sqlCount);
            if (list.size() > 0) {
                int countNum = ((BigDecimal) list.get(0).get("COUNT_NUM")).intValue();
                return countNum;
            } else {
                return 0;
            }
        }
    
        /**
         * 處理sql語句
         *
         * @param _strSql
         * @return
         */
        public String toSql(String _strSql) {
            String strNewSql = _strSql;
    
            if (strNewSql != null) {
                strNewSql = regReplace("'", "''", strNewSql);
            } else {
                strNewSql = "";
            }
    
            return strNewSql;
        }
    
        private String regReplace(String strFind, String strReplacement, String strOld) {
            String strNew = strOld;
            Pattern p = null;
            Matcher m = null;
            try {
                p = Pattern.compile(strFind);
                m = p.matcher(strOld);
                strNew = m.replaceAll(strReplacement);
            } catch (Exception e) {
            }
    
            return strNew;
        }
    
        /**
         * 根據hql語句查詢資料
         *
         * @param hql
         * @return
         */
        @SuppressWarnings("rawtypes")
        public List queryForList(String hql, List<Object> params) {
            EntityManager entityManager = entityManagerFactory.createEntityManager();
            Query query = entityManager.createQuery(hql);
            List list = null;
            try {
                if (params != null && !params.isEmpty()) {
                    for (int i = 0, size = params.size(); i < size; i++) {
                        query.setParameter(i + 1, params.get(i));
                    }
                }
                list = query.getResultList();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                entityManager.close();
            }
            return list;
        }
    
        @SuppressWarnings("rawtypes")
        public List queryByMapParams(String hql, Map<String, Object> params, Integer currentPage, Integer pageSize) {
            EntityManager entityManager = entityManagerFactory.createEntityManager();
            Query query = entityManager.createQuery(hql);
            List list = null;
            try {
                if (params != null && !params.isEmpty()) {
                    for (Map.Entry<String, Object> entry : params.entrySet()) {
                        query.setParameter(entry.getKey(), entry.getValue());
                    }
                }
    
                if (currentPage != null && pageSize != null) {
                    query.setFirstResult((currentPage - 1) * pageSize);
                    query.setMaxResults(pageSize);
                }
                list = query.getResultList();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                entityManager.close();
            }
    
            return list;
        }
    
        @SuppressWarnings("rawtypes")
        public List queryByMapParams(String hql, Map<String, Object> params) {
            return queryByMapParams(hql, params, null, null);
        }
    
        @SuppressWarnings("rawtypes")
        public List queryForList(String hql) {
            return queryForList(hql, null);
        }
    
    
        /**
         * 查詢總數
         *
         * @param hql
         * @param params
         * @return
         */
        public Long queryCount(String hql, Map<String, Object> params) {
            EntityManager entityManager = entityManagerFactory.createEntityManager();
            Query query = entityManager.createQuery(hql);
            Long count = null;
            try {
                if (params != null && !params.isEmpty()) {
                    for (Map.Entry<String, Object> entry : params.entrySet()) {
                        query.setParameter(entry.getKey(), entry.getValue());
                    }
                }
                count = (Long) query.getSingleResult();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                entityManager.close();
            }
    
            return count;
        }
    
        /**
         * 查詢總數
         *
         * @param sql
         * @param params
         * @return
         */
        public Integer queryCountBySql(String sql, Map<String, Object> params) {
            EntityManager entityManager = entityManagerFactory.createEntityManager();
            Integer count = null;
            try {
                Query query = entityManager.createNativeQuery(sql);
                if (params != null && !params.isEmpty()) {
                    for (Map.Entry<String, Object> entry : params.entrySet()) {
                        query.setParameter(entry.getKey(), entry.getValue());
                    }
                }
    
                Object obj = query.getSingleResult();
                if (obj instanceof BigDecimal) {
                    count = ((BigDecimal) obj).intValue();
                } else {
                    count = (Integer) obj;
                }
    
            } finally {
                if (entityManager != null) {
                    entityManager.close();
                }
            }
            return count;
        }
    
        /**
         * select count(*) from table
         *
         * @param sql
         * @param params
         * @return
         */
        public int executeSql(String sql, List<Object> params) {
            EntityManager entityManager = entityManagerFactory.createEntityManager();
            try {
                Query query = entityManager.createNativeQuery(sql);
                if (params != null && !params.isEmpty()) {
                    for (int i = 0, size = params.size(); i < size; i++) {
                        query.setParameter(i + 1, params.get(i));
                    }
                }
                return query.executeUpdate();
            } finally {
                if (entityManager != null) {
                    entityManager.close();
                }
            }
        }
    }
    複製程式碼

我們在service層注入,然後就可以根據輸入條件拼接好sql或者hql來進行各種操作。這種方式靈活而且也不需要手動寫分頁程式碼,使用hibernate封裝好的機制即可。

總結

使用boot可以快速搭建一個前後端開發的骨架,裡面有很多自動的配置和約定。雖然boot不是新的一個技術棧,但是它要求我們對各個元件都要比較熟悉,不然對它的執行機制和約定配置會感到很困惑。而使用JPA進行資料庫操作也是利弊參半,需要自己權衡。

專案程式碼:github.com/gongxufan/t…


相關文章