View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.shardingsphere.mode.repository.standalone.jdbc;
19  
20  import com.google.common.base.Strings;
21  import com.zaxxer.hikari.HikariConfig;
22  import com.zaxxer.hikari.HikariDataSource;
23  import com.zaxxer.hikari.util.PropertyElf;
24  import lombok.SneakyThrows;
25  import lombok.extern.slf4j.Slf4j;
26  import org.apache.shardingsphere.mode.repository.standalone.StandalonePersistRepository;
27  import org.apache.shardingsphere.mode.repository.standalone.jdbc.props.JDBCRepositoryProperties;
28  import org.apache.shardingsphere.mode.repository.standalone.jdbc.props.JDBCRepositoryPropertyKey;
29  import org.apache.shardingsphere.mode.repository.standalone.jdbc.sql.JDBCRepositorySQL;
30  import org.apache.shardingsphere.mode.repository.standalone.jdbc.sql.JDBCRepositorySQLLoader;
31  
32  import java.sql.Connection;
33  import java.sql.PreparedStatement;
34  import java.sql.ResultSet;
35  import java.sql.SQLException;
36  import java.sql.Statement;
37  import java.util.ArrayList;
38  import java.util.Arrays;
39  import java.util.Collections;
40  import java.util.Comparator;
41  import java.util.LinkedList;
42  import java.util.List;
43  import java.util.Properties;
44  import java.util.UUID;
45  
46  /**
47   * JDBC repository.
48   */
49  @Slf4j
50  public final class JDBCRepository implements StandalonePersistRepository {
51      
52      private static final String SEPARATOR = "/";
53      
54      private JDBCRepositorySQL repositorySQL;
55      
56      private HikariDataSource dataSource;
57      
58      @SneakyThrows(SQLException.class)
59      @Override
60      public void init(final Properties props) {
61          JDBCRepositoryProperties jdbcRepositoryProps = new JDBCRepositoryProperties(props);
62          repositorySQL = JDBCRepositorySQLLoader.load(jdbcRepositoryProps.getValue(JDBCRepositoryPropertyKey.PROVIDER));
63          dataSource = new HikariDataSource(createHikariConfiguration(props, jdbcRepositoryProps));
64          try (
65                  Connection connection = dataSource.getConnection();
66                  Statement statement = connection.createStatement()) {
67              statement.execute(repositorySQL.getCreateTableSQL());
68              // TODO remove it later. Add for reset standalone test e2e's env. Need to close DataSource to release H2's memory data
69              if (jdbcRepositoryProps.<String>getValue(JDBCRepositoryPropertyKey.JDBC_URL).contains("h2:mem:")) {
70                  try {
71                      statement.execute("TRUNCATE TABLE `repository`");
72                  } catch (final SQLException ignored) {
73                  }
74              }
75              // Finish TODO
76          }
77      }
78      
79      private HikariConfig createHikariConfiguration(final Properties props, final JDBCRepositoryProperties jdbcRepositoryProps) {
80          HikariConfig result = new HikariConfig(copyProperties(props));
81          result.setDriverClassName(repositorySQL.getDriverClassName());
82          result.setJdbcUrl(jdbcRepositoryProps.getValue(JDBCRepositoryPropertyKey.JDBC_URL));
83          result.setUsername(jdbcRepositoryProps.getValue(JDBCRepositoryPropertyKey.USERNAME));
84          result.setPassword(jdbcRepositoryProps.getValue(JDBCRepositoryPropertyKey.PASSWORD));
85          return result;
86      }
87      
88      private Properties copyProperties(final Properties props) {
89          Properties result = PropertyElf.copyProperties(props);
90          result.remove(JDBCRepositoryPropertyKey.PROVIDER.getKey());
91          result.remove(JDBCRepositoryPropertyKey.JDBC_URL.getKey());
92          result.remove(JDBCRepositoryPropertyKey.USERNAME.getKey());
93          result.remove(JDBCRepositoryPropertyKey.PASSWORD.getKey());
94          return result;
95      }
96      
97      @Override
98      public String query(final String key) {
99          try (
100                 Connection connection = dataSource.getConnection();
101                 PreparedStatement preparedStatement = connection.prepareStatement(repositorySQL.getSelectByKeySQL())) {
102             preparedStatement.setString(1, key);
103             try (ResultSet resultSet = preparedStatement.executeQuery()) {
104                 if (resultSet.next()) {
105                     return resultSet.getString("value");
106                 }
107             }
108         } catch (final SQLException ex) {
109             log.error("Get {} data by key: {} failed", getType(), key, ex);
110         }
111         return "";
112     }
113     
114     @Override
115     public List<String> getChildrenKeys(final String key) {
116         try (
117                 Connection connection = dataSource.getConnection();
118                 PreparedStatement preparedStatement = connection.prepareStatement(repositorySQL.getSelectByParentKeySQL())) {
119             preparedStatement.setString(1, key);
120             try (ResultSet resultSet = preparedStatement.executeQuery()) {
121                 List<String> resultChildren = new LinkedList<>();
122                 while (resultSet.next()) {
123                     String childrenKey = resultSet.getString("key");
124                     if (Strings.isNullOrEmpty(childrenKey)) {
125                         continue;
126                     }
127                     int lastIndexOf = childrenKey.lastIndexOf(SEPARATOR);
128                     resultChildren.add(childrenKey.substring(lastIndexOf + 1));
129                 }
130                 resultChildren.sort(Comparator.reverseOrder());
131                 return new ArrayList<>(resultChildren);
132             }
133         } catch (final SQLException ex) {
134             log.error("Get children {} data by key: {} failed", getType(), key, ex);
135             return Collections.emptyList();
136         }
137     }
138     
139     @Override
140     public boolean isExisted(final String key) {
141         try (
142                 Connection connection = dataSource.getConnection();
143                 PreparedStatement preparedStatement = connection.prepareStatement(repositorySQL.getSelectByKeySQL())) {
144             preparedStatement.setString(1, key);
145             try (ResultSet resultSet = preparedStatement.executeQuery()) {
146                 return resultSet.next();
147             }
148         } catch (final SQLException ex) {
149             log.error("Check existence of {} data by key: {} failed", getType(), key, ex);
150         }
151         return false;
152     }
153     
154     @Override
155     public void persist(final String key, final String value) {
156         try {
157             if (isExisted(key)) {
158                 update(key, value);
159                 return;
160             }
161             String tempPrefix = "";
162             String parent = SEPARATOR;
163             String[] paths = Arrays.stream(key.split(SEPARATOR)).filter(each -> !Strings.isNullOrEmpty(each)).toArray(String[]::new);
164             // Create key level directory recursively.
165             for (int i = 0; i < paths.length - 1; i++) {
166                 String tempKey = tempPrefix + SEPARATOR + paths[i];
167                 if (!isExisted(tempKey)) {
168                     insert(tempKey, "", parent);
169                 }
170                 tempPrefix = tempKey;
171                 parent = tempKey;
172             }
173             insert(key, value, parent);
174         } catch (final SQLException ex) {
175             log.error("Persist {} data to key: {} failed", getType(), key, ex);
176         }
177     }
178     
179     private void insert(final String key, final String value, final String parent) throws SQLException {
180         try (
181                 Connection connection = dataSource.getConnection();
182                 PreparedStatement preparedStatement = connection.prepareStatement(repositorySQL.getInsertSQL())) {
183             preparedStatement.setString(1, UUID.randomUUID().toString());
184             preparedStatement.setString(2, key);
185             preparedStatement.setString(3, value);
186             preparedStatement.setString(4, parent);
187             preparedStatement.executeUpdate();
188         }
189     }
190     
191     @Override
192     public void update(final String key, final String value) {
193         try (
194                 Connection connection = dataSource.getConnection();
195                 PreparedStatement preparedStatement = connection.prepareStatement(repositorySQL.getUpdateSQL())) {
196             preparedStatement.setString(1, value);
197             preparedStatement.setString(2, key);
198             preparedStatement.executeUpdate();
199         } catch (final SQLException ex) {
200             log.error("Update {} data to key: {} failed", getType(), key, ex);
201         }
202     }
203     
204     /**
205      * Delete the specified row.
206      * Once the database connection involved in this row of data has been closed by other threads and this row of data is located in the H2Database started in memory mode,
207      * the data is actually deleted.
208      *
209      * @param key key of data
210      */
211     @Override
212     public void delete(final String key) {
213         if (dataSource.isClosed() && dataSource.getJdbcUrl().startsWith("jdbc:h2:mem:")) {
214             return;
215         }
216         try (
217                 Connection connection = dataSource.getConnection();
218                 PreparedStatement preparedStatement = connection.prepareStatement(repositorySQL.getDeleteSQL())) {
219             preparedStatement.setString(1, key + "%");
220             preparedStatement.executeUpdate();
221         } catch (final SQLException ex) {
222             log.error("Delete {} data by key: {} failed", getType(), key, ex);
223         }
224     }
225     
226     @Override
227     public void close() {
228         dataSource.close();
229     }
230     
231     @Override
232     public String getType() {
233         return "JDBC";
234     }
235 }