数据库中控制某一字段字符长度的应用
在工作中遇到了这么一个场景:
在数据库中需要对表的某个字段进行字符追加,并且字符追加在字段前端,对字符的长度有限制,
例如:保持字段的长度在100以内,每次追加固定字符,在字段的前面,当字段超过100时,截掉超出的部分。
根据实际的情况,我们提出了3种解决方案:
1 实现一种queue,保持其中的容量固定,并最后序列化进数据库,当需要插入字符,从数据库中取出,反序列化,向queue中插入数据,
并且当数据超过容量时自动截掉最先插入的数据,最后再将其序列化进入数据库;
2 编写原生的mysql语句,对字符串进行操作;
3 由于项目中采用JPA,因此找到一种jpql语句,与2实现功效相当。
经过思考,比较了三种方案的优缺点:
方案一:容易实现,java实现,通用性也不错,但是当插入字符串时,对数据库操作存在事务性,当并发操作时,会产生问题。
方案二:采用一条sql语句实现,简单,没有事务性存在,不用考虑并发的问题,但是如果数据库更换,需要重写sql语句。
方案三:采用标准的jpql语句实现,是一种标准,对于数据库更换不存在修改问题,没有事务性,不需要考虑并发问题。
综上所述,采用第三种方案是最优的。
在实验过程中,实现了方案一和方案三:
方案一:
用于存放字符串的queue
1 import java.io.Serializable;
2 import java.util.ArrayList;
3 import java.util.LinkedList;
4 import java.util.List;
5
6 public class StrsQueue implements Serializable {
7
8 /**
9 *
10 */
11 private static final long serialVersionUID = -204264456311340382L;
12
13 private int totalSize;// 记录queue的容量
14 private LinkedList<String> queue;
15
16 public StrsQueue (int totalSize) {
17 queue = new LinkedList<String>();
18 this.totalSize=totalSize;
19 }
20
21
22
23 public void addStr(String str) {
24 queue.add(0, str);
25 if (queue.size() > totalSize) {
26 queue.removeLast();//超过容量,删掉最先插入的数据
27 }
28 }
29
30 public List<String> obtainStrs( int pageNumber,int pageSize) {
31 int offset=(pageNumber-1)*pageSize;
32 if (offset >= totalSize) {
33 return null;
34 }
35 ArrayList<String> list = new ArrayList<String>();
36 int length=Math.min(queue.size(), totalSize);
37 // int length = (queue.size() < totalSize) ? queue.size() : totalSize;
38 if ((offset + pageNumber) > length || pageSize == 0) {
39 for (int i = offset; i < length; i++) {
40 String feedId = (String) queue.get(i);
41 list.add(feedId);
42 }
43 } else {
44 for (int i = offset; i < pageSize; i++) {
45 list.add((String) queue.get(i));
46 }
47 }
48 return list;
49 }
50
51 }
使用StrQueue的Entity,
2
3 import java.io.ByteArrayInputStream;
4 import java.io.ByteArrayOutputStream;
5 import java.io.IOException;
6 import java.io.ObjectInputStream;
7 import java.io.ObjectOutputStream;
8
9 import javax.persistence.Column;
10 import javax.persistence.Entity;
11 import javax.persistence.Id;
12 import javax.persistence.Table;
13 import javax.persistence.Transient;
14
15
16 /**
17 * UserFeedInbox entity. @author MyEclipse Persistence Tools
18 */
19 @Entity
20 @Table(name = "table_name", catalog = "database")
21 public class UserFeedInboxEntry implements java.io.Serializable {
22 // Fields
23
24
25 private static final long serialVersionUID = 840144016005162535L;
26 private String id;
27 private String userId;
28 private String userType;
29 private byte[] strs;
30
31 private StrsQueue strsQueue;
32
33 // Constructors
34
35 /** default constructor */
36 public UserFeedInboxEntry() {
37 }
38
39 /** minimal constructor */
40 public UserFeedInboxEntry(String id) {
41 this.id = id;
42 }
43
44 /** full constructor */
45 public UserFeedInboxEntry(String id, String userId, String userType,
46 byte[] strs) {
47 this.id = id;
48 this.userId = userId;
49 this.userType = userType;
50 this.strs = strs;
51 }
52
53 // Property accessors
54 @Id
55 @Column(name = "id", unique = true, nullable = false, length = 36)
56 public String getId() {
57 return this.id;
58 }
59
60 public void setId(String id) {
61 this.id = id;
62 }
63
64 @Column(name = "user_id", length = 36)
65 public String getUserId() {
66 return this.userId;
67 }
68
69 public void setUserId(String userId) {
70 this.userId = userId;
71 }
72
73 @Column(name = "user_type", length = 50)
74 public String getUserType() {
75 return this.userType;
76 }
77
78 public void setUserType(String userType) {
79 this.userType = userType;
80 }
81
82 @Column(name = "strs", columnDefinition = "blob")
83 public byte[] getStrs() {
84 return this.strs;
85 }
86
87 public void setStrs(byte[] strs) {
88 this.strs = strs;
89
90 ByteArrayInputStream bais;
91 ObjectInputStream in;
92 try {
93 bais = new ByteArrayInputStream(strs);
94 in = new ObjectInputStream(bais);
95 strsQueue = (StrsQueue) in.readObject();
96 in.close();
97 } catch (IOException ex) {
98 ex.printStackTrace();
99 } catch (ClassNotFoundException ex) {
100 ex.printStackTrace();
101 }
102 }
103
104 @Transient
105 public StrsQueue getFeedsIdsQueue() {
106 return strsQueue;
107 }
108
109 public void setStrsQueue(StrsQueue strsQueue) {
110 this.strsQueue = strsQueue;
111 ByteArrayOutputStream baos;
112 ObjectOutputStream out;
113 baos = new ByteArrayOutputStream();
114 try {
115 out = new ObjectOutputStream(baos);
116 out.writeObject(strsQueue);
117 out.close();
118 } catch (IOException e) {
119 e.printStackTrace();
120 }
121 this.strs = baos.toByteArray();
122 }
123
124 }
注:开始时设计的Queue中,采用的底层实现是ArrayList,在queue中记录一个position变量,插入字符串时直接插入到arraylist的尾部,并将position指到当前位置,当queue的字符串的数量达到容量时,position指向arraylist的0位置,从头开始set值,这样能够保证容量不变,并且不需要对list进行删除操作,当初做这种设计是考虑当容量大时,用linkedlist虽然插入和删除动作对链表结构性能影响不大,但是如果从链表中取值的话需要遍历,如果取的值很多,并且取值的位置不固定时,linkedlist的取值效率没有arraylist直接根据下标取值的效率高,因此最初将底层设计为arraylist,但是最后跟头讨论了一下,本项目的queue的容量没有那么大,linkedlist取值的效率问题不需要考虑,因此改为采用linkedlist实现,代码比较清晰易懂。
方案三:本项目采用spring data for jpa 实现,
2
3 import org.springframework.data.jpa.repository.Modifying;
4 import org.springframework.data.jpa.repository.Query;
5 import org.springframework.data.repository.CrudRepository;
6 import org.springframework.data.repository.query.Param;
7
9
10 public interface MyEntryJpaRepo extends CrudRepository<MyEntry, String> {
//根据offset和length获取字符串
28 @Query("SELECT SUBSTRING(m.strs,:offset, :length) FROM MyEntry m WHERE m.userId = :userId AND m.userType = :userType")
29 public String obtainStrs(@Param("userId")String userId, @Param("userType")String userType,@Param("offset")int offset,@Param("length")int length);
30
31 @Modifying
32 @Query("UPDATE MyEntry m SET m.strs = CONCAT(CONCAT(:str ,','),SUBSTRING(m.strs,1,:strsLength)) WHERE m.userId = :userId AND m.userType = :userType")
33 public void addStrToInbox(@Param("userId")String userId, @Param("userType")String userType,@Param("str")String str,@Param("idsLength")int idsLength);
34
35 }
最后做了单元测试,就不一一介绍了,但是测试时发现个问题,当spring data jpa中的offset为负值时,数据从字段的后面开始数并截取相应长度的字符串,当offset为0时,返回空串“”。