001package io.ebean; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.List; 006import java.util.Objects; 007 008/** 009 * Holds a list of value object pairs. 010 * <p> 011 * This feature is to enable use of L2 cache with complex natural keys with findList() queries in cases where the 012 * IN clause is not a single property but instead a pair of properties. 013 * </p> 014 * <p> 015 * These queries can have predicates that can be translated into a list of complex natural keys such that the L2 016 * cache can be hit with these keys to obtain some or all of the beans from L2 cache rather than the DB. 017 * </p> 018 * <pre>{@code 019 * 020 * // where a bean is annotated with a complex 021 * // natural key made of several properties 022 * @Cache(naturalKey = {"store","code","sku"}) 023 * 024 * 025 * Pairs pairs = new Pairs("sku", "code"); 026 * pairs.add("sj2", 1000); 027 * pairs.add("sj2", 1001); 028 * pairs.add("pf3", 1000); 029 * 030 * List<OCachedNatKeyBean3> list = DB.find(OCachedNatKeyBean3.class) 031 * .where() 032 * .eq("store", "def") 033 * .inPairs(pairs) // IN clause with 'pairs' of values 034 * .order("sku desc") 035 * 036 * // query expressions cover the natural key properties 037 * // so we can choose to hit the L2 bean cache if we want 038 * .setUseCache(true) 039 * .findList(); 040 * 041 * }</pre> 042 * <h3>Important implementation Note</h3> 043 * <p> 044 * When binding many pairs of values we want to be able to utilise a DB index (as this type of query usually means the 045 * pairs are a unique key/index or part of a unique key/index and highly selective). Currently we know we can do this 046 * on any DB that supports expression/formula based indexes. 047 * using a DB string concatenation formula 048 * </p> 049 * <p> 050 * This means, the implementation converts the list of pairs into a list of strings via concatenation and we use a 051 * DB concatenation formula to match. We see SQL like: 052 * </p> 053 * <pre>{@code sql 054 * 055 * ... 056 * where t0.store = ? and (t0.sku||'-'||t0.code) in (?, ? ) 057 * 058 * // bind values like: "sj2-1000", "pf3-1000" 059 * 060 * }</pre> 061 * <p> 062 * We often create a DB expression index to match the DB concat formula like: 063 * </p> 064 * <pre>{@code sql 065 * 066 * create index ix_name on table_name (sku || '-' || code); 067 * 068 * }</pre> 069 */ 070public final class Pairs { 071 072 private final String property0; 073 private final String property1; 074 private final List<Entry> entries = new ArrayList<>(); 075 076 /** 077 * Character between the values when combined via DB varchar concatenation. 078 */ 079 private String concatSeparator = "-"; 080 081 /** 082 * Optional suffix added to DB varchar concatenation formula. 083 */ 084 private String concatSuffix; 085 086 /** 087 * Create with 2 property names. 088 * 089 * @param property0 The property of the first value 090 * @param property1 The property of the second value 091 */ 092 public Pairs(String property0, String property1) { 093 this.property0 = property0; 094 this.property1 = property1; 095 } 096 097 /** 098 * Add a pair of value objects. 099 * <p> 100 * Both values are expected to be immutable with equals and hashCode implementations. 101 * </p> 102 * 103 * @param a Value of the first property 104 * @param b Value of the second property 105 */ 106 public Pairs add(Object a, Object b) { 107 entries.add(new Entry(a, b)); 108 return this; 109 } 110 111 /** 112 * Return the first property name. 113 */ 114 public String property0() { 115 return property0; 116 } 117 118 /** 119 * Return the second property name. 120 */ 121 public String property1() { 122 return property1; 123 } 124 125 /** 126 * Return all the value pairs. 127 */ 128 public List<Entry> entries() { 129 return Collections.unmodifiableList(entries); 130 } 131 132 /** 133 * Return the separator character used with DB varchar concatenation to combine the 2 values. 134 */ 135 public String concatSeparator() { 136 return concatSeparator; 137 } 138 139 /** 140 * Set the separator character used with DB varchar concatenation to combine the 2 values. 141 */ 142 public Pairs concatSeparator(String concatSeparator) { 143 this.concatSeparator = concatSeparator; 144 return this; 145 } 146 147 /** 148 * Deprecated migrate to concatSeparator() 149 */ 150 @Deprecated 151 public Pairs setConcatSeparator(String concatSeparator) { 152 return concatSeparator(concatSeparator); 153 } 154 155 /** 156 * Return a suffix used with DB varchar concatenation to combine the 2 values. 157 */ 158 public String concatSuffix() { 159 return concatSuffix; 160 } 161 162 /** 163 * Add a suffix used with DB varchar concatenation to combine the 2 values. 164 */ 165 public Pairs concatSuffix(String concatSuffix) { 166 this.concatSuffix = concatSuffix; 167 return this; 168 } 169 170 /** 171 * Deprecated migrate to concatSuffix() 172 */ 173 @Deprecated 174 public Pairs setConcatSuffix(String concatSuffix) { 175 return concatSuffix(concatSuffix); 176 } 177 178 @Override 179 public String toString() { 180 return "p0:" + property0 + " p1:" + property1 + " entries:" + entries; 181 } 182 183 /** 184 * A pair of 2 value objects. 185 * <p> 186 * Used to support inPairs() expression. 187 */ 188 public static class Entry { 189 190 private final Object a; 191 private final Object b; 192 193 /** 194 * Create with values for property0 and property1 respectively. 195 * 196 * @param a Value of the first property 197 * @param b Value of the second property 198 */ 199 public Entry(Object a, Object b) { 200 this.a = a; 201 this.b = b; 202 } 203 204 @Override 205 public String toString() { 206 return "{" + a + "," + b + "}"; 207 } 208 209 /** 210 * Return the value for the first property. 211 */ 212 public Object getA() { 213 return a; 214 } 215 216 /** 217 * Return the value for the second property. 218 */ 219 public Object getB() { 220 return b; 221 } 222 223 @Override 224 public boolean equals(Object o) { 225 if (this == o) return true; 226 if (o == null || getClass() != o.getClass()) return false; 227 Entry that = (Entry) o; 228 return a.equals(that.a) && b.equals(that.b); 229 } 230 231 @Override 232 public int hashCode() { 233 return Objects.hash(a, b); 234 } 235 } 236}