springboot+liquibase+hibernate

本文讲述基于spring boot + hibernate + liquibase + mysql + REST 快速搭建开发环境。 代码在github代码

项目骨架

项目骨架图

pom 依赖jar包


     <!--hibernate 和 java EE jpa 依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!--liquibase 依赖-->
    <dependency>
        <groupId>org.liquibase</groupId>
        <artifactId>liquibase-core</artifactId>
    </dependency>

    <!--h2数据库 单元测试用 -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>test</scope>
    </dependency>

    <!--mysql数据库 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency><!--h2数据库 单元测试用 -->

    <!--spring rest-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

     <!--spring boot test-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
     <!--faxtxml json 核心包-->
    <dependency>
        <artifactId>jackson-databind</artifactId>
        <groupId>com.fasterxml.jackson.core</groupId>
    </dependency>
     <!--faxtxml json 注解包-->
    <dependency>
        <groupId>com.fasterxml.jackson.module</groupId>
        <artifactId>jackson-module-jaxb-annotations</artifactId>
    </dependency>
    <!--faxtxml json 注解包核心包-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
    </dependency>

配置spring boot properties配置



    server:
        port: 13003
        servlet-path: /spring

    spring:
        datasource:
            url: jdbc:mysql://127.0.0.1:3306/springboot_hibernate_demo?characterEncoding=UTF-8&useSSL=false&createDatabaseIfNotExist=true
            username: root
            password:
            tomcat:
                test-on-borrow: true
                validation-query: SELECT 1
                max-active: 50
                max-idle: 20
                min-idle: 15
                initial-size: 1
        jpa:
            hibernate: 
                ddl-auto: none
                dialect: org.hibernate.dialect.MySQLDialect
                naming:
                    physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
                format_sql: true

    liquibase:
        changeLog: "classpath:db/changelog/master.xml"


    logging:
        level:
            com.hibernate: DEBUG
            org.hibernate.SQL: DEBUG

写liquibase xml


在 resources/db/changelog/ 新建master.xml文件


    <?xml version="1.0" encoding="UTF-8"?>
    <databaseChangeLog
            xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
            xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
        http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">

    <property name="now" value="CURRENT_TIMESTAMP"/>
    <property name="emailType" value="VARCHAR(255)"/>

    <include file="db/changelog/2017-10-09-init.xml"/>

    </databaseChangeLog>


然后新建 2017-10-09-init.xml


    <?xml version="1.0" encoding="UTF-8"?>
    <databaseChangeLog
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
        http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
        <changeSet id="utf8" author="whoknows">
            <sql>
                ALTER DATABASE springboot_hibernate_demo CHARACTER SET utf8 COLLATE utf8_general_ci;
            </sql>
        </changeSet>
        <changeSet author="wanli zhou" id="1">
            <createTable tableName="province">
                <column autoIncrement="true" name="id" type="bigint">
                    <constraints nullable="false" primaryKey="true" primaryKeyName="chemical_structure_pk"/>
                </column>
                <column name="code" type="varchar(6)">
                    <constraints nullable="true"/>
                </column>
                <column name="name" type="varchar(40)">
                    <constraints nullable="true"/>
                </column>
            </createTable>
        </changeSet>

    <changeSet author="wanli zhou" id="2">
        <createTable tableName="city">
            <column autoIncrement="true" name="id" type="bigint">
                <constraints nullable="false" primaryKey="true" primaryKeyName="chemical_structure_pk"/>
            </column>
            <column name="code" type="varchar(15)">
                <constraints nullable="true"/>
            </column>
            <column name="name" type="varchar(15)">
                <constraints nullable="true"/>
            </column>
            <column name="province_id" type="bigint">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>

    <changeSet author="wanli zhou" id="3">
        <createTable tableName="county">
            <column autoIncrement="true" name="id" type="bigint">
                <constraints nullable="false" primaryKey="true" primaryKeyName="chemical_structure_pk"/>
            </column>
            <column name="code" type="varchar(15)">
                <constraints nullable="true"/>
            </column>
            <column name="name" type="varchar(15)">
                <constraints nullable="true"/>
            </column>

            <column name="city_id" type="bigint">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    </databaseChangeLog>

写hibernate domain entity 类


这里有一个很重要的细节, 如果直接用实体bean来序列化成json返回给前端的话,因为 实体bean中有 一对多,多对多,一对一等关联,因此在json序列化的时候很容易产生死循环序,引发栈溢出。产生如下exception:


    Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: com.hibernate.demo.domain.Province["cities"]->org.hibernate.collection.internal.PersistentBag[0]->com.hibernate.demo.domain.City["province"]->com.hibernate.demo.domain.Province["cities"]->org.hibernate.collection.internal.PersistentBag[0]


解决方法 加上 @JsonManagedReference 和 @JsonBackReference, 这样序列化的时候,就会忽略 @JsonBackReference注解的字段,从而剪掉闭环,为树型。 问题描述和解决方法


    package com.hibernate.demo.domain;

    import com.fasterxml.jackson.annotation.JsonManagedReference;

    import javax.persistence.*;
    import java.io.Serializable;
    import java.util.List;

    /**
     * @author wanli zhou
     * @created 2017-10-09 11:02 PM.
     */
    @Entity
    @Table(name = "province")
    public class Province implements Serializable {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;
        private String name;
        private String code;

        @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "province")
        @JsonManagedReference
        List<City> cities;


        public List<City> getCities() {
            return cities;
        }

        public void setCities(List<City> cities) {
            this.cities = cities;
        }

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getCode() {
            return code;
        }

        public void setCode(String code) {
            this.code = code;
        }
    }


    package com.hibernate.demo.domain;

    import com.fasterxml.jackson.annotation.JsonBackReference;
    import com.fasterxml.jackson.annotation.JsonManagedReference;

    import javax.persistence.*;
    import java.io.Serializable;
    import java.util.List;

    /**
     * @author wanli zhou
     * @created 2017-10-09 11:02 PM.
     */
    @Entity
    @Table(name = "city")
    public class City implements Serializable{

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;
        private String name;
        private String code;
        @ManyToOne
        @JoinColumn(name = "province_id")
        @JsonBackReference
        private Province province;


        @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "city")
        @JsonManagedReference
        List<County> counties;


        public List<County> getCounties() {
            return counties;
        }

        public void setCounties(List<County> counties) {
            this.counties = counties;
        }

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getCode() {
            return code;
        }

        public void setCode(String code) {
            this.code = code;
        }

        public Province getProvince() {
            return province;
        }

        public void setProvince(Province province) {
            this.province = province;
        }
    }

    package com.hibernate.demo.domain;

    import com.fasterxml.jackson.annotation.JsonBackReference;

    import javax.persistence.*;
    import java.io.Serializable;

    /**
    * @author wanli zhou
    * @created 2017-10-09 11:02 PM.
    */
    @Entity
    @Table(name = "county")
    public class County implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;
    private String code;
    @ManyToOne
    @JoinColumn(name = "city_id")
    @JsonBackReference
    private City city;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public City getCity() {
        return city;
    }

    public void setCity(City city) {
        this.city = city;
    }
    }

写dao 层和 service层(这里为了简化合并在一起了)


    package com.hibernate.demo.controller;

    import com.hibernate.demo.domain.City;
    import com.hibernate.demo.domain.County;
    import com.hibernate.demo.domain.Province;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import javax.xml.bind.Marshaller;
    import javax.persistence.EntityManager;
    import javax.persistence.PersistenceContext;

    /**
     * @author wanli zhou
     * @created 2017-10-09 11:11 PM.
     */
    @Service
    @Transactional
    public class AddressService {

        @PersistenceContext
        private EntityManager em;

        public Province getProvinceById(Integer id) {

            Province p = em.createQuery("SELECT p FROM Province p WHERE p.id = :id", Province.class)
                    .setParameter("id", id)
                    .getResultList().stream().findFirst().orElse(null);
            return p;
        }

        public void setEm(EntityManager em) {
            this.em = em;
        }

        public Province getProvinceByName(String name) {
            Province p  = em.createQuery("SELECT p FROM Province p WHERE p.name like :name", Province.class)
                    .setParameter("name", "%" + name + "%")
                    .getResultList().stream().findFirst().orElse(null);
            return p;
        }

        public City getCityByName(String name) {
            City c =  em.createQuery("SELECT c FROM City c WHERE c.name like :name", City.class)
                    .setParameter("name", "%" + name + "%")
                    .getResultList().stream().findFirst().orElse(null);
            return c;

        }

        public County getCountyByName(String name) {
            County c =  em.createQuery("SELECT c FROM County c WHERE c.name like :name", County.class)
                    .setParameter("name", "%" + name + "%")
                    .getResultList().stream().findFirst().orElse(null);
            return c;
        }
    }

写controller层的REST


    package com.hibernate.demo.controller;

    import com.hibernate.demo.domain.City;
    import com.hibernate.demo.domain.County;
    import com.hibernate.demo.domain.Province;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;

    /**
     * @author wanli zhou
     * @created 2017-10-09 11:00 PM.
     */
    @Controller
    @RequestMapping(path = "/address/", consumes = "application/json", produces = "application/json")
    public class AddressController {

        private final AddressService addressService;

        @Autowired
        public AddressController(AddressService addressService){
            this.addressService = addressService;
        }


        @GetMapping("province/id/{id}")
        public ResponseEntity getProvinceById(@PathVariable("id") Integer id){
            Province pi = addressService.getProvinceById(id);
            System.out.println(pi);
            return ResponseEntity.ok(pi);
        }
        @GetMapping("province/{name}")
        public ResponseEntity getProvinceByName(@PathVariable("name") String name){
            Province pi = addressService.getProvinceByName(name);
            System.out.println(pi);
            return ResponseEntity.ok(pi);
        }

        @GetMapping("city/{name}")
        public ResponseEntity getCityByName(@PathVariable("name") String name){
            City ci = addressService.getCityByName(name);
            System.out.println(ci);
            return ResponseEntity.ok(ci);
        }

        @GetMapping("county/{name}")
        public ResponseEntity getCountyByName(@PathVariable("name") String name){
            County ci = addressService.getCountyByName(name);
            System.out.println(ci);
            return ResponseEntity.ok(ci);
        }
    }

完结

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
慷慨打赏