001package io.ebean; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.List; 006 007/** 008 * Holds a list of value object pairs. 009 * <p> 010 * This feature is to enable use of L2 cache with complex natural keys with findList() queries in cases where the 011 * IN clause is not a single property but instead a pair of properties. 012 * </p> 013 * <p> 014 * These queries can have predicates that can be translated into a list of complex natural keys such that the L2 015 * cache can be hit with these keys to obtain some or all of the beans from L2 cache rather than the DB. 016 * </p> 017 * <pre>{@code 018 * 019 * // where a bean is annotated with a complex 020 * // natural key made of several properties 021 * @Cache(naturalKey = {"store","code","sku"}) 022 * 023 * 024 * Pairs pairs = new Pairs("sku", "code"); 025 * pairs.add("sj2", 1000); 026 * pairs.add("sj2", 1001); 027 * pairs.add("pf3", 1000); 028 * 029 * List<OCachedNatKeyBean3> list = DB.find(OCachedNatKeyBean3.class) 030 * .where() 031 * .eq("store", "def") 032 * .inPairs(pairs) // IN clause with 'pairs' of values 033 * .order("sku desc") 034 * 035 * // query expressions cover the natural key properties 036 * // so we can choose to hit the L2 bean cache if we want 037 * .setUseCache(true) 038 * .findList(); 039 * 040 * }</pre> 041 * <h3>Important implementation Note</h3> 042 * <p> 043 * When binding many pairs of values we want to be able to utilise a DB index (as this type of query usually means the 044 * pairs are a unique key/index or part of a unique key/index and highly selective). Currently we know we can do this 045 * on any DB that supports expression/formula based indexes. 046 * using a DB string concatenation formula 047 * </p> 048 * <p> 049 * This means, the implementation converts the list of pairs into a list of strings via concatenation and we use a 050 * DB concatenation formula to match. We see SQL like: 051 * </p> 052 * <pre>{@code sql 053 * 054 * ... 055 * where t0.store = ? and (t0.sku||'-'||t0.code) in (?, ? ) 056 * 057 * // bind values like: "sj2-1000", "pf3-1000" 058 * 059 * }</pre> 060 * <p> 061 * We often create a DB expression index to match the DB concat formula like: 062 * </p> 063 * <pre>{@code sql 064 * 065 * create index ix_name on table_name ((sku || '-' || code)); 066 * 067 * }</pre> 068 */ 069public class Pairs { 070 071 private final String property0; 072 private final String property1; 073 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 getProperty0() { 115 return property0; 116 } 117 118 /** 119 * Return the second property name. 120 */ 121 public String getProperty1() { 122 return property1; 123 } 124 125 /** 126 * Return all the value pairs. 127 */ 128 public List<Entry> getEntries() { 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 getConcatSeparator() { 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 setConcatSeparator(String concatSeparator) { 143 this.concatSeparator = concatSeparator; 144 return this; 145 } 146 147 /** 148 * Return a suffix used with DB varchar concatenation to combine the 2 values. 149 */ 150 public String getConcatSuffix() { 151 return concatSuffix; 152 } 153 154 /** 155 * Add a suffix used with DB varchar concatenation to combine the 2 values. 156 */ 157 public Pairs setConcatSuffix(String concatSuffix) { 158 this.concatSuffix = concatSuffix; 159 return this; 160 } 161 162 @Override 163 public String toString() { 164 return "p0:" + property0 + " p1:" + property1 + " entries:" + entries; 165 } 166 167 /** 168 * A pair of 2 value objects. 169 * <p> 170 * Used to support inPairs() expression. 171 */ 172 public static class Entry { 173 174 private final Object a; 175 private final Object b; 176 177 /** 178 * Create with values for property0 and property1 respectively. 179 * 180 * @param a Value of the first property 181 * @param b Value of the second property 182 */ 183 public Entry(Object a, Object b) { 184 this.a = a; 185 this.b = b; 186 } 187 188 @Override 189 public String toString() { 190 return "{" + a + "," + b + "}"; 191 } 192 193 /** 194 * Return the value for the first property. 195 */ 196 public Object getA() { 197 return a; 198 } 199 200 /** 201 * Return the value for the second property. 202 */ 203 public Object getB() { 204 return b; 205 } 206 207 @Override 208 public boolean equals(Object o) { 209 if (this == o) return true; 210 if (o == null || getClass() != o.getClass()) return false; 211 212 Entry that = (Entry) o; 213 return a.equals(that.a) && b.equals(that.b); 214 } 215 216 @Override 217 public int hashCode() { 218 int result = a.hashCode(); 219 result = 92821 * result + b.hashCode(); 220 return result; 221 } 222 } 223}