项目搭建
首先搭建一个基础项目进行策略
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 <?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 > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.2.4.RELEASE</version > <relativePath /> </parent > <groupId > cn.idea360</groupId > <artifactId > spring-boot-hibernate</artifactId > <version > 0.0.1-SNAPSHOT</version > <name > spring-boot-hibernate</name > <description > Application to integrate Spring Boot with Hibernate</description > <properties > <java.version > 1.8</java.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-jpa</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <scope > runtime</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
配置mysql数据库
1 2 3 4 5 6 7 spring.datasource.url =jdbc:mysql://localhost:3306/spring-boot-hibernate?useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC spring.datasource.username =root spring.datasource.password =root spring.datasource.driver-class-name =com.mysql.jdbc.Driver spring.jpa.hibernate.ddl-auto =update spring.jpa.properties.hibernate.dialect =org.hibernate.dialect.MySQL8Dialect
数据库初始化
通过如下配置即可自动创建表
1 2 spring.jpa.generate-ddl =true spring.jpa.hibernate.ddl-auto =update
这里DDL先手动配置为主键自增类型, 后续测试不自增下的情形
1 2 3 4 5 6 7 8 9 10 DROP TABLE IF EXISTS `customer`;CREATE TABLE `customer` ( `id` bigint (20 ) NOT NULL AUTO_INCREMENT, `created_time` datetime(6 ) NOT NULL , `email` varchar (255 ) DEFAULT NULL , `first_name` varchar (255 ) DEFAULT NULL , `last_name` varchar (255 ) DEFAULT NULL , `updated_time` datetime(6 ) NOT NULL , PRIMARY KEY (`id`) ) ENGINE= InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci;
创建JPA 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 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 package cn.idea360.data;import org.springframework.data.annotation.CreatedDate;import org.springframework.data.annotation.LastModifiedDate;import org.springframework.data.jpa.domain.support.AuditingEntityListener;import javax.persistence.*;import java.time.LocalDateTime;@Entity @EntityListeners(AuditingEntityListener.class) public class Customer { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) private Long id; private String firstName; private String lastName; private String email; @Column(updatable = false, nullable = false) @CreatedDate private LocalDateTime createdTime; @Column(nullable = false) @LastModifiedDate private LocalDateTime updatedTime; public Customer () { } public Customer (String firstName, String lastName, String email) { this .firstName = firstName; this .lastName = lastName; this .email = email; } public void setId (Long id) { this .id = id; } public Long getId () { return id; } public String getFirstName () { return firstName; } public void setFirstName (String firstName) { this .firstName = firstName; } public String getLastName () { return lastName; } public void setLastName (String lastName) { this .lastName = lastName; } public String getEmail () { return email; } public void setEmail (String email) { this .email = email; } public LocalDateTime getCreatedTime () { return createdTime; } public void setCreatedTime (LocalDateTime createdTime) { this .createdTime = createdTime; } public LocalDateTime getUpdatedTime () { return updatedTime; } public void setUpdatedTime (LocalDateTime updatedTime) { this .updatedTime = updatedTime; } @Override public String toString () { return "Customer{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + '}' ; } }
创建Repository
1 2 3 4 5 6 7 package cn.idea360.repository;import cn.idea360.data.Customer;import org.springframework.data.jpa.repository.JpaRepository;public interface CustomerRepository extends JpaRepository <Customer ,Long > {}
创建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 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 package cn.idea360.service;import cn.idea360.data.Customer;import cn.idea360.dto.CustomerData;import cn.idea360.repository.CustomerRepository;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import javax.persistence.EntityNotFoundException;import java.util.ArrayList;import java.util.List;@Service("customerService") public class DefaultCustomerService implements CustomerService { @Autowired private CustomerRepository customerRepository; @Override public CustomerData saveCustomer (CustomerData customer) { Customer customerModel = populateCustomerEntity(customer); return populateCustomerData(customerRepository.save(customerModel)); } @Override public boolean deleteCustomer (Long customerId) { customerRepository.deleteById(customerId); return true ; } @Override public List<CustomerData> getAllCustomers () { List<CustomerData> customers = new ArrayList<>(); List<Customer> customerList = customerRepository.findAll(); customerList.forEach(customer -> { customers.add(populateCustomerData(customer)); }); return customers; } @Override public CustomerData getCustomerById (Long customerId) { return populateCustomerData( customerRepository.findById(customerId).orElseThrow(() -> new EntityNotFoundException("Customer not found" ))); } private CustomerData populateCustomerData (final Customer customer) { CustomerData customerData = new CustomerData(); customerData.setId(customer.getId()); customerData.setFirstName(customer.getFirstName()); customerData.setLastName(customer.getLastName()); customerData.setEmail(customer.getEmail()); return customerData; } private Customer populateCustomerEntity (CustomerData customerData) { Customer customer = new Customer(); customer.setFirstName(customerData.getFirstName()); customer.setLastName(customerData.getLastName()); customer.setEmail(customerData.getEmail()); return customer; } }
创建Controller
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 package cn.idea360.controller;import cn.idea360.dto.CustomerData;import cn.idea360.service.CustomerService;import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;import java.util.List;@RestController @RequestMapping("/customers") public class CustomerController { @Resource(name = "customerService") private CustomerService customerService; @GetMapping public List<CustomerData> getCustomers () { return customerService.getAllCustomers(); } @GetMapping("/customer/{id}") public CustomerData getCustomer (@PathVariable Long id) { return customerService.getCustomerById(id); } @PostMapping("/customer") public CustomerData saveCustomer (final @RequestBody CustomerData customerData) { return customerService.saveCustomer(customerData); } @DeleteMapping("/customer/{id}") public Boolean deleteCustomer (@PathVariable Long id) { return customerService.deleteCustomer(id); } }
启动类
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 package cn.idea360;import cn.idea360.data.Customer;import cn.idea360.repository.CustomerRepository;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.boot.CommandLineRunner;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;import org.springframework.data.jpa.repository.config.EnableJpaAuditing;@SpringBootApplication @EnableJpaAuditing public class SpringBootHibernateApplication { private static final Logger log = LoggerFactory.getLogger(SpringBootHibernateApplication.class); public static void main (String[] args) { SpringApplication.run(SpringBootHibernateApplication.class, args); } }
单元测试
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 package cn.idea360;import cn.idea360.data.Customer;import cn.idea360.repository.CustomerRepository;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;@RunWith(SpringRunner.class) @SpringBootTest public class CRUDTest { @Resource private CustomerRepository customerRepository; @Test public void add () { Customer customer = new Customer(); customer.setId(7L ); customer.setFirstName("cui" ); customer.setLastName("shiying" ); customer.setEmail("idea360@foxmail.com" ); Customer save = customerRepository.save(customer); System.out.println(save.getId()); } }
开启sql打印
1 2 3 4 5 6 7 8 9 10 spring.jpa.show-sql =true spring.jpa.properties.hibernate.format_sql =true spring.jpa.properties.hibernate.generate_statistics =true logging.pattern.console =%d{yyyy-MM-dd HH:mm:ss} - %msg%n
ID策略测试1
前提
DDL中ID AUTO_INCREMENT
Entity给ID赋值
IDENTITY
1 2 @Id @GeneratedValue(strategy= GenerationType.IDENTITY)
AUTO
1 2 @Id @GeneratedValue(strategy= GenerationType.AUTO)
数据库会生成1张表 hibernate_sequence
, ID自增, 从1开始.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 mysql> select * from hibernate_sequence; + | next_val | + | 3 | + 1 row in set (0.00 sec)mysql> desc hibernate_sequence; + | Field | Type | Null | Key | Default | Extra | + | next_val | bigint (20 ) | YES | | NULL | | + 1 row in set (0.01 sec)
如果id已存在, 则报错, 如下
1 Duplicate entry '1' for key 'PRIMARY'
assigned
1 2 3 @Id @GeneratedValue(generator = "userGenerator") @GenericGenerator(name = "userGenerator", strategy = "assigned")
TABLE
1 2 3 4 5 6 7 8 9 @Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "table-generator") @TableGenerator(name = "table-generator", table = "dep_ids", pkColumnName = "table_name", valueColumnName = "seq_value", pkColumnValue = "customer", allocationSize = 1)
seq table ddl
1 2 3 4 5 6 7 DROP TABLE IF EXISTS `dep_ids`;CREATE TABLE dep_ids( `id` int NOT NULL AUTO_INCREMENT, `table_name` varchar (200 ) NOT NULL , `seq_value` bigint NOT NULL , PRIMARY KEY (`id`) );
1 Duplicate entry '1' for key 'PRIMARY'
1 2 3 4 5 6 7 mysql> select * from dep_ids; + | id | table_name | seq_value | + | 1 | customer | 1 | + 1 row in set (0.00 sec)
hibernate内置主键生成策略
org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public DefaultIdentifierGeneratorFactory () { register( "uuid2" , UUIDGenerator.class ); register( "guid" , GUIDGenerator.class ); register( "uuid" , UUIDHexGenerator.class ); register( "uuid.hex" , UUIDHexGenerator.class ); register( "assigned" , Assigned.class ); register( "identity" , IdentityGenerator.class ); register( "select" , SelectGenerator.class ); register( "sequence" , SequenceStyleGenerator.class ); register( "seqhilo" , SequenceHiLoGenerator.class ); register( "increment" , IncrementGenerator.class ); register( "foreign" , ForeignGenerator.class ); register( "sequence-identity" , SequenceIdentityGenerator.class ); register( "enhanced-sequence" , SequenceStyleGenerator.class ); register( "enhanced-table" , TableGenerator.class ); }
自定义雪花算法生成策略
1 2 3 @Id @GeneratedValue(generator = "snowFlakeId") @GenericGenerator(name = "snowFlakeId", strategy = "cn.idea360.generate.SnowballIdGenerator")
雪花算法实现
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 package cn.idea360.generate;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.net.InetAddress;import java.net.UnknownHostException;import java.util.Random;public class SnowballIdWorker { private static final Logger LOG = LoggerFactory.getLogger(SnowballIdWorker.class); protected final long twepoch = 1288834974657L ; private final long workerIdBits = 10L ; private final long maxWorkerId = -1L ^ (-1L << workerIdBits); private final long sequenceBits = 12L ; private final long workerIdShift = sequenceBits; private final long timestampLeftShift = sequenceBits + workerIdBits; private final long sequenceMask = -1L ^ (-1L << sequenceBits); protected long workerId = 0 ; protected long sequence = 0L ; private long lastTimestamp = -1L ; { byte [] address; try { address = InetAddress.getLocalHost().getAddress(); } catch (UnknownHostException e) { address = null ; } if (address != null ) { for (byte x : address) { workerId = ((workerId << 8 ) - Byte.MIN_VALUE + x) & maxWorkerId; } } else { LOG.warn("Cannot get ip address for generating server id, use random address instead." ); workerId = new Random().nextLong() & maxWorkerId; } LOG.info("Worker starting. Timestamp left shift {}, worker id bits {}, sequence bits {}, worker id {}." , timestampLeftShift, workerIdBits, sequenceBits, workerId); } protected synchronized long nextId () { long timestamp = timeGen(); if (timestamp < lastTimestamp) { LOG.error("Clock is moving backwards. Rejecting requests until {}." , lastTimestamp); throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds" , lastTimestamp - timestamp)); } if (lastTimestamp == timestamp) { sequence = (sequence + 1 ) & sequenceMask; if (sequence == 0 ) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0 ; } lastTimestamp = timestamp; return ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence; } protected long tilNextMillis (long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } protected long timeGen () { return System.currentTimeMillis(); } }
ID生成策略实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package cn.idea360.generate;import org.hibernate.HibernateException;import org.hibernate.engine.spi.SharedSessionContractImplementor;import org.hibernate.id.IdentifierGenerator;import java.io.Serializable;public class SnowballIdGenerator implements IdentifierGenerator { private final SnowballIdWorker worker = new SnowballIdWorker(); @Override public Serializable generate (SharedSessionContractImplementor session, Object object) throws HibernateException { return worker.nextId(); } }
自定义多级ID生成策略
如果入参ID有值, 则按id值入库
如果入参ID==null, 则按table写入
首先我们初始化ddl后开始测试, 分别测试id为空, id有值的场景
1 2 3 4 5 6 7 8 9 @Id @GeneratedValue(generator = "id_gen") @GenericGenerator(name = "id_gen", strategy = "cn.idea360.generate.CustomGenerator", parameters = { @Parameter( name = "table_name", value = "enhanced_gen"), @Parameter( name ="value_column_name", value = "next"), @Parameter( name = "segment_column_name",value = "segment_name"), @Parameter( name = "increment_size", value = "1") })
enhanced_gen会自动生成
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 package cn.idea360.generate;import org.hibernate.HibernateException;import org.hibernate.MappingException;import org.hibernate.engine.spi.SharedSessionContractImplementor;import org.hibernate.id.Configurable;import org.hibernate.id.enhanced.TableGenerator;import org.hibernate.service.ServiceRegistry;import org.hibernate.type.Type;import java.io.Serializable;import java.util.Properties;public class CustomGenerator extends TableGenerator implements Configurable { private String entityName; @Override public void configure (Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException { entityName = params.getProperty(ENTITY_NAME); if (entityName == null ) { throw new MappingException("no entity name" ); } params.setProperty(TableGenerator.SEGMENT_VALUE_PARAM, params.getProperty(JPA_ENTITY_NAME)); super .configure(type, params, serviceRegistry); } @Override public Serializable generate (SharedSessionContractImplementor session, Object obj) throws HibernateException { final Serializable id = session.getEntityPersister(entityName, obj).getIdentifier(obj, session); if (id == null ) { return super .generate(session, obj); } return id; } }
自定义多级ID生成策略2(补充)
如果入参ID有值, 则按id值入库
如果入参ID==null, 则按Identity写入
1 2 3 4 5 @Id @Column(name = "id") @GeneratedValue(generator = CustomIdentityGenerator.CUSTOM_IDENTITY_GENERATOR) @GenericGenerator(name = CustomIdentityGenerator.CUSTOM_IDENTITY_GENERATOR, strategy = CustomIdentityGenerator.CUSTOM_IDENTITY_STRATEGY) public Long id;
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 public class CustomIdentityGenerator extends IdentityGenerator implements Configurable { public static final String CUSTOM_IDENTITY_GENERATOR = "custom_identity" ; public static final String CUSTOM_IDENTITY_STRATEGY = "org.ueom.eom.data.generator.CustomIdentityGenerator" ; private String entityName; @Override public void configure (Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException { entityName = params.getProperty(ENTITY_NAME); if (entityName == null ) { throw new MappingException("no entity name" ); } params.setProperty(TableGenerator.SEGMENT_VALUE_PARAM, params.getProperty("target_table" )); } @Override public Serializable generate (SharedSessionContractImplementor session, Object obj) throws HibernateException { final Serializable id = session.getEntityPersister(entityName, obj).getIdentifier(obj, session); if (id == null || (id instanceof Number && 0L == ((Number)id).longValue())) { return super .generate(session, obj); } return id; } }
ID空测试
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 mysql> show tables; + | Tables_in_spring- boot- hibernate | + | customer | | enhanced_gen | + 2 rows in set (0.00 sec)mysql> desc enhanced_gen; + | Field | Type | Null | Key | Default | Extra | + | segment_name | varchar (255 ) | NO | PRI | NULL | | | next | bigint (20 ) | YES | | NULL | | + 2 rows in set (0.00 sec)mysql> select * from enhanced_gen; + | segment_name | next | + | customer | 2 | + 1 row in set (0.00 sec)mysql> select * from customer; + | id | created_time | email | first_name | last_name | updated_time | + | 1 | 2023 -04 -25 08 :49 :31.707081 | idea360@foxmail .com | cui | shiying | 2023 -04 -25 08 :49 :31.707081 | | 2 | 2023 -04 -25 08 :52 :08.651403 | idea360@foxmail .com | cui | shiying | 2023 -04 -25 08 :52 :08.651403 | + 2 rows in set (0.00 sec)
ID赋值测试
1 2 3 4 5 6 7 8 9 mysql> select * from customer; + | id | created_time | email | first_name | last_name | updated_time | + | 1 | 2023 -04 -25 08 :49 :31.707081 | idea360@foxmail .com | cui | shiying | 2023 -04 -25 08 :49 :31.707081 | | 2 | 2023 -04 -25 08 :52 :08.651403 | idea360@foxmail .com | cui | shiying | 2023 -04 -25 08 :52 :08.651403 | | 7 | 2023 -04 -25 08 :54 :14.444484 | idea360@foxmail .com | cui | shiying | 2023 -04 -25 08 :54 :14.444484 | + 3 rows in set (0.00 sec)
ID策略测试2
前提
IDENTITY
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
1 Field 'id' doesn't have a default value
AUTO
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
1 ID由hibernate_sequence控制, 从1开始
assigned
@Id
@GeneratedValue(generator = "userGenerator")
@GenericGenerator(name = "userGenerator", strategy = "assigned")
TABLE
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "table-generator")
@TableGenerator(name = "table-generator",
table = "dep_ids",
pkColumnName = "table_name",
valueColumnName = "seq_value",
pkColumnValue = "customer",
allocationSize = 1)
1 Duplicate entry '1' for key 'PRIMARY'
自定义主键生成策略
@Id
@GeneratedValue(generator = "snowFlakeId")
@GenericGenerator(name = "snowFlakeId", strategy = "cn.idea360.generate.SnowballIdGenerator")
参考