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.HikariDataSource;
22  import lombok.SneakyThrows;
23  import lombok.extern.slf4j.Slf4j;
24  import org.apache.shardingsphere.mode.repository.standalone.StandalonePersistRepository;
25  import org.apache.shardingsphere.mode.repository.standalone.jdbc.props.JDBCRepositoryProperties;
26  import org.apache.shardingsphere.mode.repository.standalone.jdbc.props.JDBCRepositoryPropertyKey;
27  import org.apache.shardingsphere.mode.repository.standalone.jdbc.sql.JDBCRepositorySQL;
28  import org.apache.shardingsphere.mode.repository.standalone.jdbc.sql.JDBCRepositorySQLLoader;
29  
30  import java.sql.Connection;
31  import java.sql.PreparedStatement;
32  import java.sql.ResultSet;
33  import java.sql.SQLException;
34  import java.sql.Statement;
35  import java.util.ArrayList;
36  import java.util.Arrays;
37  import java.util.Collections;
38  import java.util.Comparator;
39  import java.util.LinkedList;
40  import java.util.List;
41  import java.util.Properties;
42  import java.util.UUID;
43  
44  /**
45   * JDBC repository.
46   */
47  @Slf4j
48  public final class JDBCRepository implements StandalonePersistRepository {
49      
50      private static final String SEPARATOR = "/";
51      
52      private JDBCRepositorySQL repositorySQL;
53      
54      private HikariDataSource dataSource;
55      
56      @SneakyThrows(SQLException.class)
57      @Override
58      public void init(final Properties props) {
59          JDBCRepositoryProperties jdbcRepositoryProps = new JDBCRepositoryProperties(props);
60          repositorySQL = JDBCRepositorySQLLoader.load(jdbcRepositoryProps.getValue(JDBCRepositoryPropertyKey.PROVIDER));
61          dataSource = new HikariDataSource();
62          dataSource.setDriverClassName(repositorySQL.getDriverClassName());
63          dataSource.setJdbcUrl(jdbcRepositoryProps.getValue(JDBCRepositoryPropertyKey.JDBC_URL));
64          dataSource.setUsername(jdbcRepositoryProps.getValue(JDBCRepositoryPropertyKey.USERNAME));
65          dataSource.setPassword(jdbcRepositoryProps.getValue(JDBCRepositoryPropertyKey.PASSWORD));
66          try (
67                  Connection connection = dataSource.getConnection();
68                  Statement statement = connection.createStatement()) {
69              // TODO remove it later. Add for reset standalone test e2e's env. Need to close DataSource to release H2's memory data
70              if (jdbcRepositoryProps.<String>getValue(JDBCRepositoryPropertyKey.JDBC_URL).contains("h2:mem:")) {
71                  try {
72                      statement.execute("TRUNCATE TABLE `repository`");
73                  } catch (final SQLException ignored) {
74                  }
75              }
76              // Finish TODO
77              statement.execute(repositorySQL.getCreateTableSQL());
78          }
79      }
80      
81      @Override
82      public String getDirectly(final String key) {
83          try (
84                  Connection connection = dataSource.getConnection();
85                  PreparedStatement preparedStatement = connection.prepareStatement(repositorySQL.getSelectByKeySQL())) {
86              preparedStatement.setString(1, key);
87              try (ResultSet resultSet = preparedStatement.executeQuery()) {
88                  if (resultSet.next()) {
89                      return resultSet.getString("value");
90                  }
91              }
92          } catch (final SQLException ex) {
93              log.error("Get {} data by key: {} failed", getType(), key, ex);
94          }
95          return "";
96      }
97      
98      @Override
99      public List<String> getChildrenKeys(final String key) {
100         try (
101                 Connection connection = dataSource.getConnection();
102                 PreparedStatement preparedStatement = connection.prepareStatement(repositorySQL.getSelectByParentKeySQL())) {
103             preparedStatement.setString(1, key);
104             try (ResultSet resultSet = preparedStatement.executeQuery()) {
105                 List<String> resultChildren = new LinkedList<>();
106                 while (resultSet.next()) {
107                     String childrenKey = resultSet.getString("key");
108                     if (Strings.isNullOrEmpty(childrenKey)) {
109                         continue;
110                     }
111                     int lastIndexOf = childrenKey.lastIndexOf(SEPARATOR);
112                     resultChildren.add(childrenKey.substring(lastIndexOf + 1));
113                 }
114                 resultChildren.sort(Comparator.reverseOrder());
115                 return new ArrayList<>(resultChildren);
116             }
117         } catch (final SQLException ex) {
118             log.error("Get children {} data by key: {} failed", getType(), key, ex);
119             return Collections.emptyList();
120         }
121     }
122     
123     @Override
124     public boolean isExisted(final String key) {
125         return !Strings.isNullOrEmpty(getDirectly(key));
126     }
127     
128     @Override
129     public void persist(final String key, final String value) {
130         try {
131             if (isExisted(key)) {
132                 update(key, value);
133                 return;
134             }
135             String tempPrefix = "";
136             String parent = SEPARATOR;
137             String[] paths = Arrays.stream(key.split(SEPARATOR)).filter(each -> !Strings.isNullOrEmpty(each)).toArray(String[]::new);
138             // Create key level directory recursively.
139             for (int i = 0; i < paths.length - 1; i++) {
140                 String tempKey = tempPrefix + SEPARATOR + paths[i];
141                 String tempKeyVal = getDirectly(tempKey);
142                 if (Strings.isNullOrEmpty(tempKeyVal)) {
143                     insert(tempKey, "", parent);
144                 }
145                 tempPrefix = tempKey;
146                 parent = tempKey;
147             }
148             insert(key, value, parent);
149         } catch (final SQLException ex) {
150             log.error("Persist {} data to key: {} failed", getType(), key, ex);
151         }
152     }
153     
154     private void insert(final String key, final String value, final String parent) throws SQLException {
155         try (
156                 Connection connection = dataSource.getConnection();
157                 PreparedStatement preparedStatement = connection.prepareStatement(repositorySQL.getInsertSQL())) {
158             preparedStatement.setString(1, UUID.randomUUID().toString());
159             preparedStatement.setString(2, key);
160             preparedStatement.setString(3, value);
161             preparedStatement.setString(4, parent);
162             preparedStatement.executeUpdate();
163         }
164     }
165     
166     @Override
167     public void update(final String key, final String value) {
168         try (
169                 Connection connection = dataSource.getConnection();
170                 PreparedStatement preparedStatement = connection.prepareStatement(repositorySQL.getUpdateSQL())) {
171             preparedStatement.setString(1, value);
172             preparedStatement.setString(2, key);
173             preparedStatement.executeUpdate();
174         } catch (final SQLException ex) {
175             log.error("Update {} data to key: {} failed", getType(), key, ex);
176         }
177     }
178     
179     @Override
180     public void delete(final String key) {
181         try (
182                 Connection connection = dataSource.getConnection();
183                 PreparedStatement preparedStatement = connection.prepareStatement(repositorySQL.getDeleteSQL())) {
184             preparedStatement.setString(1, key);
185             preparedStatement.executeUpdate();
186         } catch (final SQLException ex) {
187             log.error("Delete {} data by key: {} failed", getType(), key, ex);
188         }
189     }
190     
191     @Override
192     public void close() {
193         dataSource.close();
194     }
195     
196     @Override
197     public String getType() {
198         return "JDBC";
199     }
200     
201     @Override
202     public boolean isDefault() {
203         return true;
204     }
205 }