Skip to content

Commit 873318c

Browse files
committed
Changed jsonb mapping to use JsonNode over String to avoid escaping issues.
1 parent 91f908b commit 873318c

File tree

10 files changed

+190
-54
lines changed

10 files changed

+190
-54
lines changed

roach-data-jpa-json/pom.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,26 @@
1111
<artifactId>roach-data-jpa-json</artifactId>
1212

1313
<dependencies>
14+
<dependency>
15+
<groupId>org.springframework.boot</groupId>
16+
<artifactId>spring-boot-starter-web</artifactId>
17+
</dependency>
18+
<dependency>
19+
<groupId>org.springframework.boot</groupId>
20+
<artifactId>spring-boot-starter-jetty</artifactId>
21+
</dependency>
22+
<dependency>
23+
<groupId>org.springframework.boot</groupId>
24+
<artifactId>spring-boot-starter-hateoas</artifactId>
25+
</dependency>
1426
<dependency>
1527
<groupId>org.springframework.boot</groupId>
1628
<artifactId>spring-boot-starter-jdbc</artifactId>
1729
</dependency>
30+
<dependency>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot-starter-data-jdbc</artifactId>
33+
</dependency>
1834
<dependency>
1935
<groupId>org.springframework.boot</groupId>
2036
<artifactId>spring-boot-starter-data-jpa</artifactId>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.roach.data.jpa;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.boot.WebApplicationType;
6+
import org.springframework.boot.autoconfigure.SpringBootApplication;
7+
import org.springframework.boot.builder.SpringApplicationBuilder;
8+
import org.springframework.context.annotation.EnableAspectJAutoProxy;
9+
import org.springframework.core.Ordered;
10+
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
11+
import org.springframework.hateoas.config.EnableHypermediaSupport;
12+
import org.springframework.transaction.annotation.EnableTransactionManagement;
13+
14+
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
15+
@EnableJdbcRepositories
16+
@EnableAspectJAutoProxy(proxyTargetClass = true)
17+
@EnableTransactionManagement(order = Ordered.LOWEST_PRECEDENCE - 1) // Bump up one level to enable extra advisors
18+
@SpringBootApplication
19+
public class JpaJsonApplication {
20+
protected static final Logger logger = LoggerFactory.getLogger(JpaJsonApplication.class);
21+
22+
public static void main(String[] args) {
23+
new SpringApplicationBuilder(JpaJsonApplication.class)
24+
.web(WebApplicationType.SERVLET)
25+
.run(args);
26+
}
27+
}

roach-data-jpa-json/src/main/java/io/roach/data/jpa/chat/ChatHistory.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@
88
import org.hibernate.annotations.Type;
99
import org.hibernate.annotations.TypeDef;
1010

11+
import com.fasterxml.jackson.databind.JsonNode;
12+
1113
import io.roach.data.jpa.support.AbstractJsonDataType;
1214

1315
@Entity
1416
@Table(name = "chat_history")
1517
@TypeDef(name = "jsonb-message", typeClass = ChatHistory.StringCollectionJsonType.class)
1618
public class ChatHistory {
17-
public static class StringCollectionJsonType extends AbstractJsonDataType<String> {
19+
public static class StringCollectionJsonType extends AbstractJsonDataType<JsonNode> {
1820
@Override
19-
public Class<String> returnedClass() {
20-
return String.class;
21+
public Class<JsonNode> returnedClass() {
22+
return JsonNode.class;
2123
}
2224

2325
@Override
@@ -37,7 +39,7 @@ public boolean isCollectionType() {
3739
@Type(type = "jsonb-message")
3840
@Column(name = "messages")
3941
@Basic(fetch = FetchType.LAZY)
40-
private List<String> messages;
42+
private List<JsonNode> messages;
4143

4244
public UUID getId() {
4345
return id;
@@ -51,11 +53,11 @@ public void setParent(ChatHistory parent) {
5153
this.parent = parent;
5254
}
5355

54-
public List<String> getMessages() {
56+
public List<JsonNode> getMessages() {
5557
return messages;
5658
}
5759

58-
public void setMessages(List<String> messages) {
60+
public void setMessages(List<JsonNode> messages) {
5961
this.messages = messages;
6062
}
6163
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package io.roach.data.jpa.chat;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.data.domain.PageRequest;
5+
import org.springframework.data.domain.Pageable;
6+
import org.springframework.data.domain.Sort;
7+
import org.springframework.data.web.PageableDefault;
8+
import org.springframework.data.web.PagedResourcesAssembler;
9+
import org.springframework.hateoas.CollectionModel;
10+
import org.springframework.hateoas.EntityModel;
11+
import org.springframework.hateoas.PagedModel;
12+
import org.springframework.hateoas.RepresentationModel;
13+
import org.springframework.hateoas.server.SimpleRepresentationModelAssembler;
14+
import org.springframework.http.HttpEntity;
15+
import org.springframework.http.HttpStatus;
16+
import org.springframework.http.ResponseEntity;
17+
import org.springframework.transaction.annotation.Transactional;
18+
import org.springframework.web.bind.annotation.GetMapping;
19+
import org.springframework.web.bind.annotation.RestController;
20+
21+
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
22+
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
23+
import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW;
24+
25+
@RestController
26+
public class ChatHistoryController {
27+
@Autowired
28+
private ChatHistoryRepository chatHistoryRepository;
29+
30+
@Autowired
31+
private PagedResourcesAssembler<ChatHistory> pagedResourcesAssembler;
32+
33+
@GetMapping
34+
public ResponseEntity<RepresentationModel<?>> index() {
35+
RepresentationModel<?> index = new RepresentationModel<>();
36+
37+
index.add(linkTo(methodOn(ChatHistoryController.class)
38+
.listChats(PageRequest.of(0, 5)))
39+
.withRel("chats"));
40+
41+
return new ResponseEntity<>(index, HttpStatus.OK);
42+
}
43+
44+
@GetMapping("/chathistory")
45+
@Transactional(propagation = REQUIRES_NEW)
46+
public HttpEntity<PagedModel<EntityModel<ChatHistory>>> listChats(
47+
@PageableDefault(size = 5, direction = Sort.Direction.ASC) Pageable page) {
48+
return ResponseEntity
49+
.ok(pagedResourcesAssembler.toModel(chatHistoryRepository.findAll(page), chatModelAssembler()));
50+
}
51+
52+
private SimpleRepresentationModelAssembler<ChatHistory> chatModelAssembler() {
53+
return new SimpleRepresentationModelAssembler<ChatHistory>(){
54+
@Override
55+
public void addLinks(EntityModel<ChatHistory> resource) {
56+
57+
}
58+
59+
@Override
60+
public void addLinks(CollectionModel<EntityModel<ChatHistory>> resources) {
61+
62+
}
63+
};
64+
}
65+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
########################
2+
# Spring boot properties
3+
# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
4+
########################
5+
6+
spring:
7+
output:
8+
ansi:
9+
enabled: ALWAYS
10+
11+
liquibase:
12+
change-log: classpath:db/changelog-master.xml
13+
default-schema:
14+
drop-first: false
15+
enabled: true
16+
17+
datasource:
18+
url: jdbc:postgresql://localhost:26257/roach_data?sslmode=disable
19+
driver-class-name: org.postgresql.Driver
20+
username: root
21+
password:
22+
hikari:
23+
connection-test-query: SELECT 1
24+
25+
jpa:
26+
open-in-view: false
27+
properties:
28+
hibernate:
29+
dialect: org.hibernate.dialect.CockroachDB201Dialect

roach-data-jpa-json/src/test/resources/db/create.sql renamed to roach-data-jpa-json/src/main/resources/db/create.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,9 @@ create table chat_history
7474

7575
INVERTED INDEX message_payload (messages)
7676
);
77+
78+
INSERT INTO chat_history (messages) VALUES
79+
('[
80+
{"title": "Sleeping Beauties", "genres": ["Fiction", "Thriller", "Horror"], "published": false},
81+
{"title": "The Dictator''s Handbook", "genres": ["Law", "Politics"], "authors": ["Bruce Bueno de Mesquita", "Alastair Smith"], "published": true}
82+
]');

roach-data-jpa-json/src/test/resources/logback.xml renamed to roach-data-jpa-json/src/main/resources/logback.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
<logger name="org.springframework.jdbc.core" level="DEBUG"/>
1313
<logger name="org.springframework.jdbc.core.JdbcTemplate" level="DEBUG"/>
1414

15-
<logger name="org.hibernate.SQL" level="DEBUG"/>
16-
<logger name="org.hibernate.type.descriptor.sql" level="TRACE"/>
15+
<!-- <logger name="org.hibernate.SQL" level="DEBUG"/>-->
16+
<!-- <logger name="org.hibernate.type.descriptor.sql" level="TRACE"/>-->
1717

1818
<logger name="io.roach" level="DEBUG"/>
1919

roach-data-jpa-json/src/test/java/io/roach/data/jpa/Application.java

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package io.roach.data.jpa.chat;
22

3+
import java.io.IOException;
4+
import java.io.StringWriter;
35
import java.util.Arrays;
6+
import java.util.Collections;
47
import java.util.List;
58

69
import org.junit.jupiter.api.Order;
@@ -10,38 +13,40 @@
1013
import org.springframework.transaction.annotation.Propagation;
1114
import org.springframework.transaction.annotation.Transactional;
1215

13-
import io.roach.data.jpa.AbstractIntegrationTest;
16+
import com.fasterxml.jackson.databind.JsonNode;
17+
import com.fasterxml.jackson.databind.ObjectMapper;
1418

15-
import static org.junit.jupiter.api.Assertions.assertEquals;
19+
import io.roach.data.jpa.AbstractIntegrationTest;
1620

1721
public class ChatHistoryRepositoryTest extends AbstractIntegrationTest {
1822
@Autowired
1923
private ChatHistoryRepository chatHistoryRepository;
2024

25+
@Autowired
26+
private ObjectMapper objectMapper;
27+
2128
@Test
2229
@Transactional(propagation = Propagation.REQUIRES_NEW)
2330
@Commit
2431
@Order(1)
25-
public void whenCreatingChatHistory_thenSuccess() {
26-
ChatHistory l1 = new ChatHistory();
27-
l1.setMessages(Arrays.asList("hello", "world"));
28-
29-
ChatHistory l2a = new ChatHistory();
30-
l2a.setParent(l1);
31-
l2a.setMessages(Arrays.asList("hello from", "alice"));
32-
33-
ChatHistory l2b = new ChatHistory();
34-
l2b.setParent(l1);
35-
l2b.setMessages(Arrays.asList("hello from", "bob"));
36-
37-
ChatHistory l3 = new ChatHistory();
38-
l3.setParent(l2a);
39-
l3.setMessages(Arrays.asList("hello from", "bobby tables"));
40-
41-
chatHistoryRepository.save(l1);
42-
chatHistoryRepository.save(l2a);
43-
chatHistoryRepository.save(l2b);
44-
chatHistoryRepository.save(l3);
32+
public void whenCreatingChatHistory_thenSuccess() throws Exception {
33+
String data1 = "{\"title\": \"Sleeping Beauties\", \"genres\": [\"Fiction\", \"Thriller\", \"Horror\"], \"published\": false}";
34+
String data2 = "{\"title\": \"The Dictator''s Handbook\", \"genres\": [\"Law\", \"Politics\"], \"authors\": [\"Bruce Bueno de Mesquita\", \"Alastair Smith\"], \"published\": true}";
35+
String data3 = "{\"review\": \"A good book\", \"visitor\": \"alice\"}";
36+
String data4 = "{\"review\": \"A really good book\", \"visitor\": \"bob\"}";
37+
38+
ChatHistory h1 = new ChatHistory();
39+
h1.setMessages(Arrays.asList(objectMapper.readTree(data1), objectMapper.readTree(data2)));
40+
41+
ChatHistory h2 = new ChatHistory();
42+
h2.setParent(h1);
43+
h2.setMessages(Collections.singletonList(objectMapper.readTree(data3)));
44+
45+
ChatHistory h3 = new ChatHistory();
46+
h3.setParent(h1);
47+
h3.setMessages(Collections.singletonList(objectMapper.readTree(data4)));
48+
49+
chatHistoryRepository.saveAll(Arrays.asList(h1, h2, h3));
4550
}
4651

4752
@Test
@@ -51,8 +56,16 @@ public void whenCreatingChatHistory_thenSuccess() {
5156
public void whenRetrievingById_thenReturnStuff() {
5257
List<ChatHistory> chatHistory = chatHistoryRepository.findAll();
5358
chatHistory.forEach(h -> {
54-
List<String> result = h.getMessages();
55-
assertEquals(2, result.size());
59+
List<JsonNode> result = h.getMessages();
60+
result.forEach(jsonNode -> {
61+
StringWriter w = new StringWriter();
62+
try {
63+
objectMapper.writeValue(w, jsonNode);
64+
System.out.println(w);
65+
} catch (IOException e) {
66+
e.printStackTrace();
67+
}
68+
});
5669
});
5770
}
5871
}

0 commit comments

Comments
 (0)