• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mybatis / ignite-cache / 549

14 Mar 2026 06:58PM UTC coverage: 95.652% (+20.7%) from 75.0%
549

push

github

web-flow
Merge pull request #228 from mybatis/copilot/migrate-to-ignite-3

Migrate from Apache Ignite 2.x to Ignite 3.1.0

13 of 14 branches covered (92.86%)

45 of 48 new or added lines in 1 file covered. (93.75%)

66 of 69 relevant lines covered (95.65%)

0.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.83
/src/main/java/org/mybatis/caches/ignite/IgniteCacheAdapter.java
1
/*
2
 *    Copyright 2016-2026 the original author or authors.
3
 *
4
 *    Licensed under the Apache License, Version 2.0 (the "License");
5
 *    you may not use this file except in compliance with the License.
6
 *    You may obtain a copy of the License at
7
 *
8
 *       https://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *    Unless required by applicable law or agreed to in writing, software
11
 *    distributed under the License is distributed on an "AS IS" BASIS,
12
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *    See the License for the specific language governing permissions and
14
 *    limitations under the License.
15
 */
16
package org.mybatis.caches.ignite;
17

18
import java.io.ByteArrayInputStream;
19
import java.io.ByteArrayOutputStream;
20
import java.io.IOException;
21
import java.io.InputStream;
22
import java.io.ObjectInputStream;
23
import java.io.ObjectOutputStream;
24
import java.nio.file.Files;
25
import java.nio.file.Path;
26
import java.util.Properties;
27
import java.util.concurrent.locks.ReadWriteLock;
28

29
import org.apache.ibatis.cache.Cache;
30
import org.apache.ibatis.logging.Log;
31
import org.apache.ibatis.logging.LogFactory;
32
import org.apache.ignite.catalog.ColumnType;
33
import org.apache.ignite.catalog.definitions.ColumnDefinition;
34
import org.apache.ignite.catalog.definitions.TableDefinition;
35
import org.apache.ignite.client.IgniteClient;
36
import org.apache.ignite.sql.ResultSet;
37
import org.apache.ignite.sql.SqlRow;
38
import org.apache.ignite.table.KeyValueView;
39
import org.apache.ignite.table.Tuple;
40

41
/**
42
 * Cache adapter for Ignite 3. Connects to a running Ignite 3 cluster via thin client. The server address is read from
43
 * {@value #CFG_PATH} (property {@code ignite.addresses}), otherwise the default {@value #DEFAULT_ADDRESSES} is used.
44
 *
45
 * @author Roman Shtykh
46
 */
47
public final class IgniteCacheAdapter implements Cache {
48

49
  /** Logger. */
50
  private static final Log log = LogFactory.getLog(IgniteCacheAdapter.class);
1✔
51

52
  /** Cache id. */
53
  private final String id;
54

55
  /** Table name derived from the cache id. */
56
  private final String tableName;
57

58
  /**
59
   * {@code ReadWriteLock}.
60
   */
61
  private final ReadWriteLock readWriteLock = new DummyReadWriteLock();
1✔
62

63
  /** Ignite thin client (shared across all adapter instances). Lazily initialized. */
64
  private static volatile IgniteClient sharedClient;
65

66
  /** This adapter's Ignite client. */
67
  private final IgniteClient client;
68

69
  /** Key-value view for this cache's table. */
70
  private final KeyValueView<Tuple, Tuple> cache;
71

72
  /** Default Ignite 3 thin client port. */
73
  static final String DEFAULT_ADDRESSES = "127.0.0.1:10800";
74

75
  /** Ignite client configuration file path. */
76
  static final String CFG_PATH = "config/default-config.properties";
77

78
  /** Table key column name. */
79
  static final String KEY_COL = "key";
80

81
  /** Table value column name. */
82
  static final String VAL_COL = "val";
83

84
  /**
85
   * Returns the shared {@link IgniteClient}, creating it lazily on first call.
86
   */
87
  private static IgniteClient getOrCreateIgniteClient() {
88
    if (sharedClient == null) {
1✔
89
      synchronized (IgniteCacheAdapter.class) {
1✔
90
        if (sharedClient == null) {
1!
NEW
91
          sharedClient = createIgniteClient();
×
92
        }
NEW
93
      }
×
94
    }
95
    return sharedClient;
1✔
96
  }
97

98
  /**
99
   * Creates a new {@link IgniteClient} from the configuration file or defaults.
100
   */
101
  static IgniteClient createIgniteClient() {
102
    String addresses = DEFAULT_ADDRESSES;
1✔
103
    Properties props = new Properties();
1✔
104
    try (InputStream is = Files.newInputStream(Path.of(CFG_PATH))) {
1✔
105
      props.load(is);
1✔
106
      addresses = props.getProperty("ignite.addresses", DEFAULT_ADDRESSES);
1✔
107
    } catch (IOException e) {
1✔
108
      log.debug("Ignite config file not found at '" + CFG_PATH + "', using defaults.");
1✔
109
      log.trace("" + e);
1✔
110
    }
1✔
NEW
111
    return IgniteClient.builder().addresses(addresses.split(",")).build();
×
112
  }
113

114
  /**
115
   * Constructor.
116
   *
117
   * @param id
118
   *          Cache id.
119
   */
120
  public IgniteCacheAdapter(String id) {
121
    this(requireNonNullId(id), getOrCreateIgniteClient());
1✔
122
  }
1✔
123

124
  private static String requireNonNullId(String id) {
125
    if (id == null) {
1✔
126
      throw new IllegalArgumentException("Cache instances require an ID");
1✔
127
    }
128
    return id;
1✔
129
  }
130

131
  /**
132
   * Package-private constructor for testing: allows injection of a mock {@link IgniteClient} without requiring a
133
   * running Ignite cluster.
134
   *
135
   * @param id
136
   *          Cache id.
137
   * @param igniteClient
138
   *          The {@link IgniteClient} to use.
139
   */
140
  IgniteCacheAdapter(String id, IgniteClient igniteClient) {
1✔
141
    this.id = requireNonNullId(id);
1✔
142
    this.tableName = toTableName(id);
1✔
143
    this.client = igniteClient;
1✔
144

145
    igniteClient.catalog().createTable(
1✔
146
        TableDefinition.builder(tableName).ifNotExists().columns(ColumnDefinition.column(KEY_COL, ColumnType.VARBINARY),
1✔
147
            ColumnDefinition.column(VAL_COL, ColumnType.VARBINARY)).primaryKey(KEY_COL).build());
1✔
148

149
    cache = igniteClient.tables().table(tableName).keyValueView();
1✔
150
  }
1✔
151

152
  @Override
153
  public String getId() {
154
    return this.id;
1✔
155
  }
156

157
  @Override
158
  public void putObject(Object key, Object value) {
159
    cache.put(null, Tuple.create().set(KEY_COL, serialize(key)), Tuple.create().set(VAL_COL, serialize(value)));
1✔
160
  }
1✔
161

162
  @Override
163
  public Object getObject(Object key) {
164
    Tuple valueTuple = cache.get(null, Tuple.create().set(KEY_COL, serialize(key)));
1✔
165
    return valueTuple != null ? deserialize(valueTuple.bytesValue(VAL_COL)) : null;
1✔
166
  }
167

168
  @Override
169
  public Object removeObject(Object key) {
170
    Tuple valueTuple = cache.getAndRemove(null, Tuple.create().set(KEY_COL, serialize(key)));
1✔
171
    return valueTuple != null ? deserialize(valueTuple.bytesValue(VAL_COL)) : null;
1✔
172
  }
173

174
  @Override
175
  public void clear() {
176
    cache.removeAll(null);
1✔
177
  }
1✔
178

179
  @Override
180
  public int getSize() {
181
    try (ResultSet<SqlRow> rs = client.sql().execute(null, "SELECT COUNT(*) FROM " + tableName)) {
1✔
182
      return rs.hasNext() ? (int) rs.next().longValue(0) : 0;
1✔
183
    }
184
  }
185

186
  @Override
187
  public ReadWriteLock getReadWriteLock() {
188
    return readWriteLock;
1✔
189
  }
190

191
  static String toTableName(String id) {
192
    // Sanitize to alphanumeric and underscore only, ensuring safe use in SQL identifiers.
193
    return id.replaceAll("[^a-zA-Z0-9_]", "_").toUpperCase();
1✔
194
  }
195

196
  static byte[] serialize(Object obj) {
197
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
1✔
198
        ObjectOutputStream oos = new ObjectOutputStream(baos)) {
1✔
199
      oos.writeObject(obj);
1✔
200
      return baos.toByteArray();
1✔
201
    } catch (IOException e) {
1✔
202
      throw new IllegalArgumentException("Cannot serialize object of type " + obj.getClass().getName(), e);
1✔
203
    }
204
  }
205

206
  static Object deserialize(byte[] bytes) {
207
    if (bytes == null) {
1✔
208
      return null;
1✔
209
    }
210
    try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
1✔
211
        ObjectInputStream ois = new ObjectInputStream(bais)) {
1✔
212
      return ois.readObject();
1✔
213
    } catch (IOException | ClassNotFoundException e) {
1✔
214
      throw new IllegalStateException("Cannot deserialize cache object", e);
1✔
215
    }
216
  }
217
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc