├── .gitignore ├── README.md ├── build.gradle └── src ├── main ├── java │ └── io │ │ └── brant │ │ └── example │ │ └── jdbc │ │ ├── ApplicationInitializer.java │ │ ├── SqlStatisticsController.java │ │ ├── TestController.java │ │ ├── config │ │ ├── CoreApplicationContext.java │ │ └── DataSourceConfiguration.java │ │ ├── db │ │ ├── aop │ │ │ ├── CreateStatementInterceptor.java │ │ │ ├── StatementExecutionInfo.java │ │ │ ├── StatementMethodInterceptor.java │ │ │ └── StatementType.java │ │ ├── dbcp │ │ │ ├── ProxyDataSource.java │ │ │ └── ProxyDataSourceFactory.java │ │ ├── monitor │ │ │ ├── SqlMonitoringService.java │ │ │ └── sql │ │ │ │ ├── SqlExecutionInfo.java │ │ │ │ └── SqlTaskPool.java │ │ └── utils │ │ │ ├── HibernateQueryNormalizer.java │ │ │ ├── QueryNormalizer.java │ │ │ ├── SqlFormatter.java │ │ │ └── SqlMonitoringLogUtil.java │ │ └── entity │ │ ├── User.java │ │ ├── UserRepository.java │ │ └── UserService.java └── resources │ ├── application.properties │ ├── logback.xml │ └── templates │ └── view.ftl └── test └── java └── io └── brant └── example └── jdbc └── entity └── UserServiceTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .gradle 3 | 4 | build/ 5 | classes/ 6 | target/ 7 | out/ 8 | 9 | *.ipr 10 | *.iws 11 | *.iml 12 | .idea_modules/ 13 | 14 | atlassian-ide-plugin.xml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Spring-Boot based JDBC Proxy Example 2 | ======= 3 | 4 | ``` 5 | Detecting SlowQuery & Query Statistics Example using DBCP2 Proxy 6 | ``` 7 | 8 | ###How to Open 9 | ``` 10 | IntelliJ -> Open -> build.gradle 11 | ``` 12 | 13 | ###Compile Setting 14 | ``` 15 | Open IntelliJ Preference 16 | - Build, Execution, Deployment -> Compiler 17 | -> Check 'Make project automatically' 18 | ``` 19 | 20 | ###How to Run 21 | ``` 22 | - Run main() on ApplicationInitializer Class 23 | ``` 24 | 25 | ### Environment 26 | - Java 8 27 | - Spring Boot 1.3.1 -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '1.3.1.RELEASE' 4 | } 5 | repositories { 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 10 | } 11 | } 12 | 13 | apply plugin: 'java' 14 | apply plugin: 'eclipse' 15 | apply plugin: 'idea' 16 | apply plugin: 'spring-boot' 17 | 18 | jar { 19 | baseName = 'jdbcProxy' 20 | version = '0.0.1-SNAPSHOT' 21 | } 22 | sourceCompatibility = 1.8 23 | targetCompatibility = 1.8 24 | 25 | repositories { 26 | mavenCentral() 27 | } 28 | 29 | 30 | dependencies { 31 | compile('org.springframework.boot:spring-boot-starter-data-jpa') 32 | compile('org.springframework.boot:spring-boot-starter-web') 33 | compile("org.springframework.boot:spring-boot-starter-freemarker") 34 | compile('org.apache.commons:commons-dbcp2:2.1.1') 35 | compile('org.apache.commons:commons-lang3:3.4') 36 | compile('commons-beanutils:commons-beanutils:1.9.2') 37 | runtime('com.h2database:h2') 38 | testCompile('org.springframework.boot:spring-boot-starter-test') 39 | } 40 | 41 | eclipse { 42 | classpath { 43 | containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER') 44 | containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8' 45 | } 46 | } 47 | 48 | task wrapper(type: Wrapper) { 49 | gradleVersion = '2.9' 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/ApplicationInitializer.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 6 | import org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration; 7 | 8 | @SpringBootApplication(exclude = {ErrorMvcAutoConfiguration.class, DataSourceAutoConfiguration.class}) 9 | public class ApplicationInitializer { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ApplicationInitializer.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/SqlStatisticsController.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc; 2 | 3 | 4 | import io.brant.example.jdbc.config.DataSourceConfiguration; 5 | import io.brant.example.jdbc.db.monitor.SqlMonitoringService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.ModelMap; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | 12 | @Controller 13 | public class SqlStatisticsController { 14 | 15 | @Autowired 16 | private SqlMonitoringService sqlMonitoringService; 17 | 18 | @Autowired 19 | private DataSourceConfiguration dataSourceConfiguration; 20 | 21 | @RequestMapping(value = "/sql", method = RequestMethod.GET) 22 | public String sql(ModelMap modelMap) { 23 | modelMap.put("dataSourceConfiguration", dataSourceConfiguration); 24 | modelMap.put("slowQueryThreshold", dataSourceConfiguration.getSlowQueryThreshold()); 25 | modelMap.put("sqlExecutionInfos", sqlMonitoringService.getSqlExecutionInfos()); 26 | return "view"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/TestController.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc; 2 | 3 | import io.brant.example.jdbc.entity.User; 4 | import io.brant.example.jdbc.entity.UserService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | public class TestController { 12 | 13 | @Autowired 14 | private UserService userService; 15 | 16 | @RequestMapping(value = "/api/v1/users/create") 17 | public String createUser() { 18 | User user = new User(); 19 | user.setEmail("dlstj3039@gmail.com"); 20 | user.setFirstName("Brant"); 21 | user.setLastName("Hwang"); 22 | user.setPassword("1234"); 23 | 24 | userService.create(user); 25 | 26 | return "created"; 27 | } 28 | 29 | @RequestMapping(value = "/api/v1/users/{id}") 30 | public User getUser(@PathVariable Long id) { 31 | return userService.findById(id); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/config/CoreApplicationContext.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.config; 2 | 3 | import io.brant.example.jdbc.db.dbcp.ProxyDataSourceFactory; 4 | import io.brant.example.jdbc.db.monitor.SqlMonitoringService; 5 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Primary; 9 | import org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException; 10 | 11 | import javax.sql.DataSource; 12 | 13 | @Configuration 14 | @EnableConfigurationProperties(value = DataSourceConfiguration.class) 15 | public class CoreApplicationContext { 16 | 17 | @Bean 18 | @Primary 19 | public DataSource dataSource(DataSourceConfiguration dataSourceConfiguration) { 20 | try { 21 | return ProxyDataSourceFactory.create(dataSourceConfiguration); 22 | } catch (Throwable e) { 23 | throw new DataSourceLookupFailureException("Creating dataSource Failed!"); 24 | } 25 | } 26 | 27 | @Bean 28 | public SqlMonitoringService sqlMonitoringService(DataSource dataSource) { 29 | return new SqlMonitoringService(dataSource); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/config/DataSourceConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.config; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | @ConfigurationProperties(prefix = "dataSource.custom", ignoreInvalidFields = true) 8 | public class DataSourceConfiguration { 9 | 10 | private String databaseType; 11 | private String username; 12 | private String password; 13 | private String url; 14 | private String schema; 15 | private String driverClassName; 16 | private String connectionInitSql; 17 | private int initialSize; 18 | private int minIdle; 19 | private int maxIdle; 20 | private int maxActive; 21 | private int maxWait; 22 | private boolean sqlLogging; 23 | private long slowQueryThreshold; 24 | private boolean testOnBorrow; 25 | private boolean testOnReturn; 26 | private boolean testWhileIdle; 27 | private boolean accessToUnderlyingConnectionAllowed; 28 | private long timeBetweenEvictionRunsMillis; 29 | private long minEvictableIdleTimeMillis; 30 | private long softMinEvictableIdleTimeMillis; 31 | private String dataSourceId; 32 | private int queryTimeout; 33 | private boolean queryFormatting; 34 | private boolean hibernateQueryFormatting; 35 | private String validationQuery; 36 | 37 | public String getDatabaseType() { 38 | return databaseType; 39 | } 40 | 41 | public void setDatabaseType(String databaseType) { 42 | this.databaseType = databaseType; 43 | } 44 | 45 | public String getUsername() { 46 | return username; 47 | } 48 | 49 | public void setUsername(String username) { 50 | this.username = username; 51 | } 52 | 53 | public String getPassword() { 54 | return password; 55 | } 56 | 57 | public void setPassword(String password) { 58 | this.password = password; 59 | } 60 | 61 | public String getUrl() { 62 | return url; 63 | } 64 | 65 | public void setUrl(String url) { 66 | this.url = url; 67 | } 68 | 69 | public String getSchema() { 70 | return schema; 71 | } 72 | 73 | public void setSchema(String schema) { 74 | this.schema = schema; 75 | } 76 | 77 | public String getDriverClassName() { 78 | return driverClassName; 79 | } 80 | 81 | public void setDriverClassName(String driverClassName) { 82 | this.driverClassName = driverClassName; 83 | } 84 | 85 | public String getConnectionInitSql() { 86 | return connectionInitSql; 87 | } 88 | 89 | public void setConnectionInitSql(String connectionInitSql) { 90 | this.connectionInitSql = connectionInitSql; 91 | } 92 | 93 | public int getInitialSize() { 94 | return initialSize; 95 | } 96 | 97 | public void setInitialSize(int initialSize) { 98 | this.initialSize = initialSize; 99 | } 100 | 101 | public int getMinIdle() { 102 | return minIdle; 103 | } 104 | 105 | public void setMinIdle(int minIdle) { 106 | this.minIdle = minIdle; 107 | } 108 | 109 | public int getMaxIdle() { 110 | return maxIdle; 111 | } 112 | 113 | public void setMaxIdle(int maxIdle) { 114 | this.maxIdle = maxIdle; 115 | } 116 | 117 | public int getMaxActive() { 118 | return maxActive; 119 | } 120 | 121 | public void setMaxActive(int maxActive) { 122 | this.maxActive = maxActive; 123 | } 124 | 125 | public int getMaxWait() { 126 | return maxWait; 127 | } 128 | 129 | public void setMaxWait(int maxWait) { 130 | this.maxWait = maxWait; 131 | } 132 | 133 | public boolean isSqlLogging() { 134 | return sqlLogging; 135 | } 136 | 137 | public void setSqlLogging(boolean sqlLogging) { 138 | this.sqlLogging = sqlLogging; 139 | } 140 | 141 | public long getSlowQueryThreshold() { 142 | return slowQueryThreshold; 143 | } 144 | 145 | public void setSlowQueryThreshold(long slowQueryThreshold) { 146 | this.slowQueryThreshold = slowQueryThreshold; 147 | } 148 | 149 | public boolean isTestOnBorrow() { 150 | return testOnBorrow; 151 | } 152 | 153 | public void setTestOnBorrow(boolean testOnBorrow) { 154 | this.testOnBorrow = testOnBorrow; 155 | } 156 | 157 | public boolean isTestOnReturn() { 158 | return testOnReturn; 159 | } 160 | 161 | public void setTestOnReturn(boolean testOnReturn) { 162 | this.testOnReturn = testOnReturn; 163 | } 164 | 165 | public boolean isTestWhileIdle() { 166 | return testWhileIdle; 167 | } 168 | 169 | public void setTestWhileIdle(boolean testWhileIdle) { 170 | this.testWhileIdle = testWhileIdle; 171 | } 172 | 173 | public boolean isAccessToUnderlyingConnectionAllowed() { 174 | return accessToUnderlyingConnectionAllowed; 175 | } 176 | 177 | public void setAccessToUnderlyingConnectionAllowed(boolean accessToUnderlyingConnectionAllowed) { 178 | this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed; 179 | } 180 | 181 | public long getTimeBetweenEvictionRunsMillis() { 182 | return timeBetweenEvictionRunsMillis; 183 | } 184 | 185 | public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) { 186 | this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; 187 | } 188 | 189 | public long getMinEvictableIdleTimeMillis() { 190 | return minEvictableIdleTimeMillis; 191 | } 192 | 193 | public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) { 194 | this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; 195 | } 196 | 197 | public long getSoftMinEvictableIdleTimeMillis() { 198 | return softMinEvictableIdleTimeMillis; 199 | } 200 | 201 | public void setSoftMinEvictableIdleTimeMillis(long softMinEvictableIdleTimeMillis) { 202 | this.softMinEvictableIdleTimeMillis = softMinEvictableIdleTimeMillis; 203 | } 204 | 205 | public String getDataSourceId() { 206 | return dataSourceId; 207 | } 208 | 209 | public void setDataSourceId(String dataSourceId) { 210 | this.dataSourceId = dataSourceId; 211 | } 212 | 213 | public int getQueryTimeout() { 214 | return queryTimeout; 215 | } 216 | 217 | public void setQueryTimeout(int queryTimeout) { 218 | this.queryTimeout = queryTimeout; 219 | } 220 | 221 | public boolean isQueryFormatting() { 222 | return queryFormatting; 223 | } 224 | 225 | public void setQueryFormatting(boolean queryFormatting) { 226 | this.queryFormatting = queryFormatting; 227 | } 228 | 229 | public String getValidationQuery() { 230 | return validationQuery; 231 | } 232 | 233 | public void setValidationQuery(String validationQuery) { 234 | this.validationQuery = validationQuery; 235 | } 236 | 237 | public boolean isHibernateQueryFormatting() { 238 | return hibernateQueryFormatting; 239 | } 240 | 241 | public void setHibernateQueryFormatting(boolean hibernateQueryFormatting) { 242 | this.hibernateQueryFormatting = hibernateQueryFormatting; 243 | } 244 | } 245 | 246 | 247 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/db/aop/CreateStatementInterceptor.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.db.aop; 2 | 3 | 4 | import io.brant.example.jdbc.config.DataSourceConfiguration; 5 | import io.brant.example.jdbc.db.monitor.sql.SqlExecutionInfo; 6 | import io.brant.example.jdbc.db.monitor.sql.SqlTaskPool; 7 | import org.aopalliance.intercept.MethodInterceptor; 8 | import org.aopalliance.intercept.MethodInvocation; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.springframework.aop.framework.Advised; 11 | import org.springframework.aop.framework.ProxyFactory; 12 | import org.springframework.aop.support.AopUtils; 13 | 14 | import java.lang.reflect.Method; 15 | import java.sql.Statement; 16 | import java.util.Map; 17 | 18 | public class CreateStatementInterceptor implements MethodInterceptor { 19 | private static final String[] CREATE_STATEMENT_METHOD = {"createStatement", "prepareStatement", "prepareCall"}; 20 | 21 | private DataSourceConfiguration dataSourceConfiguration; 22 | 23 | private Map statementInfoMap; 24 | 25 | private SqlTaskPool sqlTaskPool; 26 | 27 | public CreateStatementInterceptor(DataSourceConfiguration dataSourceConfiguration, Map statementInfoMap, SqlTaskPool sqlTaskPool) { 28 | this.dataSourceConfiguration = dataSourceConfiguration; 29 | this.statementInfoMap = statementInfoMap; 30 | this.sqlTaskPool = sqlTaskPool; 31 | } 32 | 33 | @Override 34 | public Object invoke(MethodInvocation invocation) throws Throwable { 35 | Object returnValue = invocation.proceed(); 36 | Method invokedMethod = invocation.getMethod(); 37 | 38 | Integer queryTimeout = dataSourceConfiguration.getQueryTimeout(); 39 | 40 | if (StringUtils.indexOfAny(invokedMethod.getName(), CREATE_STATEMENT_METHOD) == -1) { 41 | return returnValue; 42 | } 43 | 44 | if (returnValue instanceof Statement) { 45 | ((Statement) returnValue).setQueryTimeout(queryTimeout); 46 | } 47 | 48 | Statement statement = getRealStatement(returnValue); 49 | 50 | if (!statementInfoMap.containsKey(statement)) { 51 | StatementExecutionInfo statementExecutionInfo = new StatementExecutionInfo(statement); 52 | 53 | if (statementExecutionInfo.getStatementType() != StatementType.statement) { 54 | String queryFormat = (String) invocation.getArguments()[0]; 55 | statementExecutionInfo.setQueryFormat(queryFormat); 56 | 57 | SqlExecutionInfo sqlExecutionInfo = sqlTaskPool.get(queryFormat); 58 | 59 | if (sqlExecutionInfo.isNew()) { 60 | sqlExecutionInfo.setDataSourceId(dataSourceConfiguration.getDataSourceId()); 61 | sqlExecutionInfo.setType(statementExecutionInfo.getStatementType()); 62 | } 63 | } 64 | statementInfoMap.put(statement, statementExecutionInfo); 65 | } 66 | 67 | ProxyFactory proxyFactory = new ProxyFactory(); 68 | proxyFactory.addAdvice(new StatementMethodInterceptor(dataSourceConfiguration, statementInfoMap, sqlTaskPool)); 69 | proxyFactory.setTarget(returnValue); 70 | proxyFactory.setInterfaces(returnValue.getClass().getInterfaces()); 71 | return proxyFactory.getProxy(); 72 | } 73 | 74 | private Statement getRealStatement(Object returnValue) throws Throwable { 75 | if (AopUtils.isAopProxy(returnValue)) { 76 | Advised advised = (Advised) returnValue; 77 | return (Statement) advised.getTargetSource().getTarget(); 78 | } 79 | return (Statement) returnValue; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/db/aop/StatementExecutionInfo.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.db.aop; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.sql.Statement; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | public class StatementExecutionInfo { 11 | StatementType statementType; 12 | String queryFormat; 13 | List currentParameters; 14 | String firstBatchQuery; 15 | AtomicInteger batchCount = new AtomicInteger(0); 16 | 17 | public StatementExecutionInfo(Statement statement) { 18 | this.statementType = StatementType.getStatementType(statement); 19 | this.currentParameters = new ArrayList<>(); 20 | } 21 | 22 | public StatementType getStatementType() { 23 | return this.statementType; 24 | } 25 | 26 | public void setQueryFormat(String queryFormat) { 27 | this.queryFormat = StringUtils.trim(queryFormat); 28 | } 29 | 30 | public String getQueryFormat() { 31 | return this.queryFormat; 32 | } 33 | 34 | public List getCurrentParameters() { 35 | return this.currentParameters; 36 | } 37 | 38 | public String getFirstBatchQuery() { 39 | return this.firstBatchQuery; 40 | } 41 | 42 | public void setFirstBatchQuery(String firstBatchQuery) { 43 | this.firstBatchQuery = firstBatchQuery; 44 | } 45 | 46 | public void incrementBatchCount() { 47 | this.batchCount.incrementAndGet(); 48 | } 49 | 50 | public int getBatchCount() { 51 | return this.batchCount.get(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/db/aop/StatementMethodInterceptor.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.db.aop; 2 | 3 | 4 | import io.brant.example.jdbc.config.DataSourceConfiguration; 5 | import io.brant.example.jdbc.db.monitor.sql.SqlExecutionInfo; 6 | import io.brant.example.jdbc.db.monitor.sql.SqlTaskPool; 7 | import io.brant.example.jdbc.db.utils.HibernateQueryNormalizer; 8 | import io.brant.example.jdbc.db.utils.QueryNormalizer; 9 | import io.brant.example.jdbc.db.utils.SqlFormatter; 10 | import org.aopalliance.intercept.MethodInterceptor; 11 | import org.aopalliance.intercept.MethodInvocation; 12 | import org.apache.commons.lang3.StringUtils; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import javax.naming.CommunicationException; 17 | import java.lang.reflect.Method; 18 | import java.net.SocketTimeoutException; 19 | import java.sql.PreparedStatement; 20 | import java.sql.Statement; 21 | import java.util.Date; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | public class StatementMethodInterceptor implements MethodInterceptor { 26 | private final Logger logger = LoggerFactory.getLogger(StatementMethodInterceptor.class); 27 | 28 | private static final String BATCH_STATEMENT_FORMAT = "%s /** count(%d) **/"; 29 | private static final String STATEMENT_METHOD_ADD_BATCH = "addBatch"; 30 | private static final String STATEMENT_METHOD_EXECUTE_BATCH = "executeBatch"; 31 | private static final String STATEMENT_METHOD_EXECUTE = "execute"; 32 | private static final String PREPARED_STATEMENT_METHOD_SET = "set"; 33 | private static final String PREPARED_STATEMENT_METHOD_SET_NULL = "setNull"; 34 | private static final String ORACLE_QUERY_TIMEOUT_CODE = "ORA-01013"; 35 | 36 | private static QueryNormalizer queryNormalizer = new QueryNormalizer(); 37 | private static HibernateQueryNormalizer hibernateQueryNormalizer = new HibernateQueryNormalizer(); 38 | private static SqlFormatter sqlFormatter = new SqlFormatter(); 39 | 40 | private Map statementInfoMap; 41 | 42 | private SqlTaskPool sqlTaskPool; 43 | 44 | private DataSourceConfiguration dataSourceConfiguration; 45 | 46 | public StatementMethodInterceptor(DataSourceConfiguration dataSourceConfiguration, Map statementInfoMap, SqlTaskPool sqlTaskPool) { 47 | this.dataSourceConfiguration = dataSourceConfiguration; 48 | this.statementInfoMap = statementInfoMap; 49 | this.sqlTaskPool = sqlTaskPool; 50 | } 51 | 52 | @Override 53 | public Object invoke(MethodInvocation invocation) throws Throwable { 54 | if (!(invocation.getThis() instanceof Statement)) { 55 | return invocation.proceed(); 56 | } 57 | Object returnValue; 58 | SqlExecutionInfo sqlExecutionInfo = null; 59 | Method invokedMethod = invocation.getMethod(); 60 | 61 | try { 62 | StatementExecutionInfo statementExecutionInfo = statementInfoMap.get(invocation.getThis()); 63 | if (statementExecutionInfo == null) { 64 | logger.error("StatementExecutionInfo is null."); 65 | return invocation.proceed(); 66 | } 67 | 68 | if (invocation.getThis() instanceof PreparedStatement && 69 | invokedMethod.getName().startsWith(PREPARED_STATEMENT_METHOD_SET)) { 70 | returnValue = invocation.proceed(); 71 | 72 | if (invocation.getArguments().length >= 2) { 73 | List parameterList = statementExecutionInfo.getCurrentParameters(); 74 | Integer position = (Integer) invocation.getArguments()[0]; 75 | 76 | while (position >= parameterList.size()) { 77 | parameterList.add(null); 78 | } 79 | 80 | if (!StringUtils.equals(invokedMethod.getName(), PREPARED_STATEMENT_METHOD_SET_NULL)) { 81 | parameterList.set(position, invocation.getArguments()[1]); 82 | } 83 | } 84 | } else if (invokedMethod.getName().equals(STATEMENT_METHOD_ADD_BATCH)) { 85 | returnValue = invocation.proceed(); 86 | 87 | if (statementExecutionInfo.getBatchCount() == 0) { 88 | String statementQuery = getQueryFromStatement(statementExecutionInfo, invocation); 89 | statementExecutionInfo.setFirstBatchQuery(statementQuery); 90 | } 91 | 92 | statementExecutionInfo.incrementBatchCount(); 93 | } else if (invokedMethod.getName().startsWith(STATEMENT_METHOD_EXECUTE)) { 94 | if (statementExecutionInfo.getStatementType() == StatementType.statement) { 95 | String statementQuery; 96 | 97 | if (invokedMethod.getName().equals(STATEMENT_METHOD_EXECUTE_BATCH)) { 98 | statementQuery = String.format(BATCH_STATEMENT_FORMAT, statementExecutionInfo.getFirstBatchQuery(), statementExecutionInfo.getBatchCount()); 99 | } else { 100 | statementQuery = getQueryFromStatement(statementExecutionInfo, invocation); 101 | } 102 | 103 | sqlExecutionInfo = sqlTaskPool.get(statementQuery); 104 | 105 | if (sqlExecutionInfo.isNew()) { 106 | sqlExecutionInfo.setDataSourceId(dataSourceConfiguration.getDataSourceId()); 107 | sqlExecutionInfo.setType(statementExecutionInfo.getStatementType()); 108 | } 109 | 110 | } else { 111 | sqlExecutionInfo = sqlTaskPool.get(statementExecutionInfo.getQueryFormat()); 112 | } 113 | 114 | long currentTaskStartTime = System.currentTimeMillis(); 115 | returnValue = invocation.proceed(); 116 | long lastTaskTime = System.currentTimeMillis() - currentTaskStartTime; 117 | 118 | if (isSlowQuery(lastTaskTime) && sqlExecutionInfo != null) { 119 | sqlExecutionInfo.addSlowQueryCount(); 120 | } 121 | 122 | if (sqlExecutionInfo != null) { 123 | sqlExecutionInfo.appendTaskTime(lastTaskTime, statementExecutionInfo.getCurrentParameters().toArray()); 124 | } 125 | 126 | if (isSlowQuery(lastTaskTime) && logger.isErrorEnabled()) { 127 | String sqlLogging = getLoggingSql(statementExecutionInfo, sqlExecutionInfo, invocation); 128 | logger.error("\n[slowQuery] - {}ms {}\n", lastTaskTime, sqlLogging); 129 | 130 | } else if (logger.isDebugEnabled()) { 131 | String sqlLogging = getLoggingSql(statementExecutionInfo, sqlExecutionInfo, invocation); 132 | logger.info("\n[query] - {} {}\n", new Date().toString(), sqlLogging); 133 | 134 | } else if (logger.isInfoEnabled() && dataSourceConfiguration.isSqlLogging()) { 135 | String sqlLogging = getLoggingSql(statementExecutionInfo, sqlExecutionInfo, invocation); 136 | logger.info("\n[query] - {} {}\n", new Date().toString(), sqlLogging); 137 | } 138 | } else { 139 | returnValue = invocation.proceed(); 140 | } 141 | } catch (Exception exception) { 142 | if (sqlExecutionInfo != null) { 143 | boolean timeoutException = false; 144 | switch (dataSourceConfiguration.getDatabaseType()) { 145 | case "mysql": 146 | if (exception instanceof CommunicationException && (exception.getCause() != null && exception.getCause() instanceof SocketTimeoutException)) { 147 | sqlExecutionInfo.addSocketTimeoutCount(); 148 | timeoutException = true; 149 | } 150 | break; 151 | case "oracle": 152 | if (StringUtils.indexOf(exception.getMessage(), ORACLE_QUERY_TIMEOUT_CODE) != -1) { 153 | sqlExecutionInfo.addQueryTimeoutCount(); 154 | timeoutException = true; 155 | } 156 | break; 157 | } 158 | if (!timeoutException) { 159 | sqlExecutionInfo.addExceptionCount(); 160 | } 161 | } 162 | throw exception; 163 | } finally { 164 | if (invocation.getMethod().getName().startsWith("close") && invocation.getThis() instanceof Statement) { 165 | statementInfoMap.remove(invocation.getThis()); 166 | } 167 | } 168 | return returnValue; 169 | } 170 | 171 | private boolean isSlowQuery(long lastTaskTime) { 172 | return lastTaskTime > dataSourceConfiguration.getSlowQueryThreshold(); 173 | } 174 | 175 | private String getQueryFromStatement(StatementExecutionInfo statementExecutionInfo, MethodInvocation methodInvocation) { 176 | String query = null; 177 | 178 | switch (statementExecutionInfo.getStatementType()) { 179 | case statement: { 180 | query = (String) methodInvocation.getArguments()[0]; 181 | break; 182 | } 183 | case preparedStatement: 184 | case callableStatement: { 185 | List parameters = statementExecutionInfo.getCurrentParameters(); 186 | String queryFormat = statementExecutionInfo.getQueryFormat(); 187 | query = queryNormalizer.format(dataSourceConfiguration.getDatabaseType(), queryFormat, parameters); 188 | break; 189 | } 190 | } 191 | return query; 192 | } 193 | 194 | private String getLoggingSql(StatementExecutionInfo statementExecutionInfo, SqlExecutionInfo sqlExecutionInfo, MethodInvocation invocation) { 195 | String sqlLogging = (statementExecutionInfo.getBatchCount() > 0) ? sqlExecutionInfo.getSql() : getQueryFromStatement(statementExecutionInfo, invocation); 196 | 197 | if (dataSourceConfiguration.isQueryFormatting()) { 198 | try { 199 | String formattedSql = sqlFormatter.format(sqlLogging); 200 | 201 | if (dataSourceConfiguration.isHibernateQueryFormatting()) { 202 | return hibernateQueryNormalizer.format(formattedSql); 203 | } 204 | 205 | return formattedSql; 206 | } catch (Exception except) { 207 | logger.debug("sql formatting exception, sql - {}", sqlLogging, except); 208 | } 209 | } 210 | return sqlLogging; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/db/aop/StatementType.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.db.aop; 2 | 3 | import java.sql.CallableStatement; 4 | import java.sql.PreparedStatement; 5 | import java.sql.Statement; 6 | 7 | public enum StatementType { 8 | statement, 9 | preparedStatement, 10 | callableStatement; 11 | 12 | public static StatementType getStatementType(Statement statement) { 13 | if (statement instanceof CallableStatement) { 14 | return callableStatement; 15 | } 16 | 17 | if (statement instanceof PreparedStatement) { 18 | return preparedStatement; 19 | } 20 | 21 | return StatementType.statement; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/db/dbcp/ProxyDataSource.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.db.dbcp; 2 | 3 | import io.brant.example.jdbc.config.DataSourceConfiguration; 4 | import io.brant.example.jdbc.db.aop.CreateStatementInterceptor; 5 | import io.brant.example.jdbc.db.aop.StatementExecutionInfo; 6 | import io.brant.example.jdbc.db.monitor.sql.SqlTaskPool; 7 | import org.apache.commons.dbcp2.BasicDataSource; 8 | import org.springframework.aop.framework.ProxyFactory; 9 | 10 | import java.sql.Connection; 11 | import java.sql.SQLException; 12 | import java.sql.SQLFeatureNotSupportedException; 13 | import java.sql.Statement; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | import java.util.logging.Logger; 16 | 17 | public class ProxyDataSource extends BasicDataSource { 18 | private SqlTaskPool sqlTaskPool = new SqlTaskPool(); 19 | 20 | private ConcurrentHashMap statementInfoMap = new ConcurrentHashMap<>(); 21 | 22 | public ConcurrentHashMap getStatementInfoMap() { 23 | return statementInfoMap; 24 | } 25 | 26 | public SqlTaskPool getSqlTaskPool() { 27 | return sqlTaskPool; 28 | } 29 | 30 | private DataSourceConfiguration dataSourceConfiguration; 31 | 32 | public void setDataSourceConfiguration(DataSourceConfiguration dataSourceConfiguration) { 33 | this.dataSourceConfiguration = dataSourceConfiguration; 34 | } 35 | 36 | public DataSourceConfiguration getDataSourceConfiguration() { 37 | return dataSourceConfiguration; 38 | } 39 | 40 | @Override 41 | public Connection getConnection() throws SQLException { 42 | Connection connection = super.getConnection(); 43 | return createProxy(connection); 44 | } 45 | 46 | @Override 47 | public Connection getConnection(String user, String pass) throws SQLException { 48 | Connection connection = super.getConnection(user, pass); 49 | return createProxy(connection); 50 | } 51 | 52 | @Override 53 | public synchronized void close() throws SQLException { 54 | super.close(); 55 | } 56 | 57 | private Connection createProxy(Connection originalConnection) { 58 | ProxyFactory proxyFactory = new ProxyFactory(); 59 | proxyFactory.setTarget(originalConnection); 60 | proxyFactory.addAdvice(new CreateStatementInterceptor(getDataSourceConfiguration(), getStatementInfoMap(), getSqlTaskPool())); 61 | proxyFactory.setInterfaces(new Class[]{Connection.class}); 62 | return (Connection) proxyFactory.getProxy(); 63 | } 64 | 65 | @Override 66 | public Logger getParentLogger() throws SQLFeatureNotSupportedException { 67 | return null; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/db/dbcp/ProxyDataSourceFactory.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.db.dbcp; 2 | 3 | import io.brant.example.jdbc.config.DataSourceConfiguration; 4 | import org.apache.commons.lang3.ArrayUtils; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.util.CollectionUtils; 9 | 10 | import java.sql.Connection; 11 | import java.util.ArrayList; 12 | import java.util.Collection; 13 | import java.util.Collections; 14 | 15 | public class ProxyDataSourceFactory { 16 | private static Logger logger = LoggerFactory.getLogger(ProxyDataSourceFactory.class); 17 | 18 | public static ProxyDataSource create(DataSourceConfiguration dataSourceConfiguration) throws Exception { 19 | try { 20 | Collection initSqls = null; 21 | if (StringUtils.isNoneEmpty(dataSourceConfiguration.getConnectionInitSql())) { 22 | String[] initSqlTokens = StringUtils.split(dataSourceConfiguration.getConnectionInitSql(), ","); 23 | 24 | if (ArrayUtils.isNotEmpty(initSqlTokens)) { 25 | initSqls = new ArrayList<>(); 26 | Collections.addAll(initSqls, initSqlTokens); 27 | } 28 | } 29 | 30 | ProxyDataSource proxyDataSource = new ProxyDataSource(); 31 | 32 | if (!CollectionUtils.isEmpty(initSqls)) { 33 | proxyDataSource.setConnectionInitSqls(initSqls); 34 | } 35 | 36 | proxyDataSource.setInitialSize(dataSourceConfiguration.getInitialSize()); 37 | proxyDataSource.setMinIdle(dataSourceConfiguration.getMinIdle()); 38 | proxyDataSource.setMaxIdle(dataSourceConfiguration.getMaxIdle()); 39 | proxyDataSource.setMaxWaitMillis(dataSourceConfiguration.getMaxWait()); 40 | proxyDataSource.setAccessToUnderlyingConnectionAllowed(dataSourceConfiguration.isAccessToUnderlyingConnectionAllowed()); 41 | proxyDataSource.setConnectionInitSqls(initSqls); 42 | proxyDataSource.setUrl(dataSourceConfiguration.getUrl()); 43 | proxyDataSource.setUsername(dataSourceConfiguration.getUsername()); 44 | proxyDataSource.setPassword(dataSourceConfiguration.getPassword()); 45 | proxyDataSource.setDriverClassName(dataSourceConfiguration.getDriverClassName()); 46 | proxyDataSource.setTestOnBorrow(dataSourceConfiguration.isTestOnBorrow()); 47 | proxyDataSource.setTestOnReturn(dataSourceConfiguration.isTestOnReturn()); 48 | proxyDataSource.setTestWhileIdle(dataSourceConfiguration.isTestWhileIdle()); 49 | proxyDataSource.setTimeBetweenEvictionRunsMillis(dataSourceConfiguration.getTimeBetweenEvictionRunsMillis()); 50 | proxyDataSource.setMinEvictableIdleTimeMillis(proxyDataSource.getMinEvictableIdleTimeMillis()); 51 | proxyDataSource.setSoftMinEvictableIdleTimeMillis(proxyDataSource.getSoftMinEvictableIdleTimeMillis()); 52 | proxyDataSource.setMaxTotal(dataSourceConfiguration.getMaxActive()); 53 | proxyDataSource.setValidationQuery(dataSourceConfiguration.getValidationQuery()); 54 | 55 | proxyDataSource.setDataSourceConfiguration(dataSourceConfiguration); 56 | 57 | Connection conn = proxyDataSource.getConnection(); 58 | conn.close(); 59 | logger.info("success to create DataSource('{}')", dataSourceConfiguration.getDataSourceId()); 60 | 61 | return proxyDataSource; 62 | 63 | } catch (Exception exception) { 64 | logger.error("fail to create DataSource('{}')", dataSourceConfiguration.getDataSourceId(), exception); 65 | throw exception; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/db/monitor/SqlMonitoringService.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.db.monitor; 2 | 3 | import io.brant.example.jdbc.db.dbcp.ProxyDataSource; 4 | import io.brant.example.jdbc.db.monitor.sql.SqlExecutionInfo; 5 | import io.brant.example.jdbc.db.utils.SqlMonitoringLogUtil; 6 | import org.springframework.beans.factory.DisposableBean; 7 | import org.springframework.beans.factory.InitializingBean; 8 | 9 | import javax.sql.DataSource; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class SqlMonitoringService implements InitializingBean, DisposableBean { 14 | 15 | private ProxyDataSource dataSource; 16 | 17 | private SqlMonitoringLogUtil sqlMonitoringLogUtil; 18 | 19 | public SqlMonitoringService(DataSource dataSource) { 20 | if (dataSource instanceof ProxyDataSource) { 21 | this.dataSource = (ProxyDataSource) dataSource; 22 | } 23 | } 24 | 25 | public List getSqlExecutionInfos() { 26 | if (this.dataSource != null) { 27 | return this.dataSource.getSqlTaskPool().getSqlExecutionInfoList(); 28 | } else { 29 | return Collections.emptyList(); 30 | } 31 | } 32 | 33 | public void saveAll() { 34 | sqlMonitoringLogUtil.saveSqlMonitoringInfo(); 35 | } 36 | 37 | @Override 38 | public void afterPropertiesSet() throws Exception { 39 | if (this.dataSource != null) { 40 | sqlMonitoringLogUtil = new SqlMonitoringLogUtil(this.dataSource.getSqlTaskPool().getSqlExecutionInfoList()); 41 | } 42 | } 43 | 44 | @Override 45 | public void destroy() throws Exception { 46 | if (this.dataSource != null) { 47 | saveAll(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/db/monitor/sql/SqlExecutionInfo.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.db.monitor.sql; 2 | 3 | import io.brant.example.jdbc.db.aop.StatementType; 4 | import io.brant.example.jdbc.db.utils.HibernateQueryNormalizer; 5 | import io.brant.example.jdbc.db.utils.SqlFormatter; 6 | import org.apache.commons.lang3.StringUtils; 7 | 8 | import java.io.Serializable; 9 | import java.util.Date; 10 | 11 | public class SqlExecutionInfo implements Serializable { 12 | private static final long serialVersionUID = -3612708782642882361L; 13 | 14 | private SqlFormatter sqlFormatter = new SqlFormatter(); 15 | private HibernateQueryNormalizer hibernateQueryNormalizer = new HibernateQueryNormalizer(); 16 | 17 | private static final long UNSET = -1; 18 | 19 | private final String sql; 20 | private final int sqlId; 21 | 22 | private StatementType type; 23 | 24 | private String dataSourceId; 25 | 26 | private long min = UNSET; 27 | private long max = UNSET; 28 | 29 | private long queryCount; 30 | private long slowQueryCount; 31 | private long exceptionCount; 32 | private long queryTimeoutCount; 33 | private long socketTimeoutCount; 34 | private long total; 35 | private long lastQueryDate; 36 | private long longestQueryDateTime; 37 | 38 | private Object[] longestQueryParams; 39 | 40 | public SqlExecutionInfo(String sql) { 41 | this.sql = StringUtils.trim(sql); 42 | this.sqlId = this.sql.hashCode(); 43 | } 44 | 45 | public boolean isNew() { 46 | return type == null; 47 | } 48 | 49 | public void setType(StatementType type) { 50 | this.type = type; 51 | } 52 | 53 | public void setDataSourceId(String dataSourceId) { 54 | this.dataSourceId = dataSourceId; 55 | } 56 | 57 | public String getDataSourceId() { 58 | return this.dataSourceId; 59 | } 60 | 61 | public int getSqlId() { 62 | return this.sqlId; 63 | } 64 | 65 | public String getSql() { 66 | return this.sql; 67 | } 68 | 69 | public String getFormattedSql(boolean hibernateQueryFormatting) { 70 | if (hibernateQueryFormatting) { 71 | return hibernateQueryNormalizer.format(sqlFormatter.format(this.sql)); 72 | } 73 | return sqlFormatter.format(this.sql); 74 | } 75 | 76 | public int getType() { 77 | return type == null ? 0 : type.ordinal(); 78 | } 79 | 80 | public synchronized void appendTaskTime(long taskTime, Object... parameterValues) { 81 | this.lastQueryDate = System.currentTimeMillis(); 82 | this.queryCount++; 83 | this.total += taskTime; 84 | 85 | if (this.max == UNSET || taskTime > max) { 86 | this.max = taskTime; 87 | this.longestQueryDateTime = System.currentTimeMillis(); 88 | if (parameterValues != null && parameterValues.length > 0) { 89 | this.longestQueryParams = parameterValues; 90 | } 91 | } 92 | 93 | if (this.min == UNSET || taskTime < min) { 94 | this.min = taskTime; 95 | } 96 | } 97 | 98 | public synchronized void addSlowQueryCount() { 99 | this.slowQueryCount++; 100 | } 101 | 102 | public synchronized void addExceptionCount() { 103 | this.exceptionCount++; 104 | } 105 | 106 | public synchronized void addQueryTimeoutCount() { 107 | this.queryTimeoutCount++; 108 | } 109 | 110 | public synchronized void addSocketTimeoutCount() { 111 | this.socketTimeoutCount++; 112 | } 113 | 114 | public synchronized long getTotal() { 115 | return this.total; 116 | } 117 | 118 | public synchronized long getMax() { 119 | return this.max; 120 | } 121 | 122 | public synchronized long getMin() { 123 | return this.min; 124 | } 125 | 126 | public synchronized long getCount() { 127 | return this.queryCount; 128 | } 129 | 130 | public synchronized long getSlowQueryCount() { 131 | return this.slowQueryCount; 132 | } 133 | 134 | public synchronized long getExceptionCount() { 135 | return this.exceptionCount; 136 | } 137 | 138 | public long getQueryTimeoutCount() { 139 | return this.queryTimeoutCount; 140 | } 141 | 142 | public long getSocketTimeoutCount() { 143 | return this.socketTimeoutCount; 144 | } 145 | 146 | public synchronized long getAverage() { 147 | return (this.total == 0 || this.queryCount <= 0) ? 0 : Math.round((double) this.total / (double) this.queryCount); 148 | } 149 | 150 | public synchronized Date getLastQueryDate() { 151 | return (this.lastQueryDate > 0) ? new Date(this.lastQueryDate) : null; 152 | } 153 | 154 | public synchronized Date getLongestQueryDateTime() { 155 | return (this.longestQueryDateTime > 0) ? new Date(this.longestQueryDateTime) : null; 156 | } 157 | 158 | public synchronized Object[] getLongestQueryParams() { 159 | return this.longestQueryParams; 160 | } 161 | 162 | @Override 163 | public synchronized String toString() { 164 | StringBuilder builder = new StringBuilder(); 165 | builder.append("sql='").append(sql).append("',").append("execution count=").append(queryCount).append(",").append("queryTimeout count=") 166 | .append(queryTimeoutCount).append(",").append("socketTimeout count=").append(socketTimeoutCount).append(",").append("average time=") 167 | .append(getAverage()).append(",").append("total time=").append(total).append(",").append("exception count=").append(exceptionCount); 168 | return builder.toString(); 169 | } 170 | 171 | @Override 172 | public boolean equals(Object obj) { 173 | if (!(obj instanceof SqlExecutionInfo)) { 174 | return false; 175 | } 176 | 177 | SqlExecutionInfo compare = (SqlExecutionInfo) obj; 178 | return sql.equals(compare.sql) && dataSourceId.equals(compare.dataSourceId); 179 | } 180 | 181 | @Override 182 | public int hashCode() { 183 | return sql.hashCode(); 184 | } 185 | 186 | 187 | } 188 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/db/monitor/sql/SqlTaskPool.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.db.monitor.sql; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.util.ArrayList; 8 | import java.util.LinkedHashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | public class SqlTaskPool { 13 | protected final Logger logger = LoggerFactory.getLogger(SqlTaskPool.class); 14 | 15 | private static final int DEFAULT_MANAGED_SIZE = 1000; 16 | 17 | private Map sqlTaskMap; 18 | 19 | public SqlTaskPool() { 20 | this(DEFAULT_MANAGED_SIZE); 21 | } 22 | 23 | public SqlTaskPool(int initialSize) { 24 | sqlTaskMap = new LinkedHashMap(initialSize) { 25 | @Override 26 | public SqlExecutionInfo get(Object key) { 27 | return super.get(StringUtils.trim((String) key)); 28 | } 29 | 30 | @Override 31 | public SqlExecutionInfo put(String key, SqlExecutionInfo value) { 32 | return super.put(StringUtils.trim(key), value); 33 | } 34 | 35 | @Override 36 | protected boolean removeEldestEntry(Map.Entry eldest) { 37 | if (size() < DEFAULT_MANAGED_SIZE) { 38 | return false; 39 | } 40 | 41 | if (logger.isDebugEnabled()) { 42 | logger.debug("too many sql query.. oldest query '{}' is discarded", eldest.getKey()); 43 | } 44 | return true; 45 | } 46 | }; 47 | } 48 | 49 | public synchronized List getSqlExecutionInfoList() { 50 | return new ArrayList<>(sqlTaskMap.values()); 51 | } 52 | 53 | public synchronized SqlExecutionInfo find(String currentSql) { 54 | return this.sqlTaskMap.get(currentSql); 55 | } 56 | 57 | public synchronized SqlExecutionInfo get(String currentSql) { 58 | SqlExecutionInfo info = sqlTaskMap.get(currentSql); 59 | if (info == null) { 60 | info = new SqlExecutionInfo(currentSql); 61 | sqlTaskMap.put(currentSql, info); 62 | } 63 | return info; 64 | } 65 | 66 | public synchronized void put(String currentSql, SqlExecutionInfo info) { 67 | sqlTaskMap.put(currentSql, info); 68 | } 69 | 70 | public synchronized void reset() { 71 | sqlTaskMap.clear(); 72 | } 73 | 74 | public synchronized int size() { 75 | return sqlTaskMap.size(); 76 | } 77 | 78 | Map getSqlTaskMap() { 79 | return sqlTaskMap; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/db/utils/HibernateQueryNormalizer.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.db.utils; 2 | 3 | public class HibernateQueryNormalizer { 4 | public static final int BLANK = 32; 5 | 6 | 7 | // 샘플구현 8 | public String format(String query) { 9 | if (query.contains("select") && query.contains("from")) { 10 | 11 | // SELECT 절에 있는 as 제거 12 | String fields = query.substring(query.indexOf("select") + 6, query.indexOf("from")).replace("\n", ""); 13 | 14 | for (String field : fields.split(",")) { 15 | String as = field.substring(field.indexOf("as ")).trim(); 16 | query = query.replaceAll(as, "").replaceAll(" ,", ","); 17 | } 18 | 19 | // FROM 절에 있는 alias와 SELECT에 있는 alias. 들을 공백으로 치환. 20 | String froms = query.substring(query.indexOf("from") + 4, query.indexOf("where")).replace("\n", ""); 21 | 22 | StringBuilder filteredFroms = new StringBuilder(); 23 | 24 | for (int i = 0; i < froms.length(); i++) { 25 | if (froms.charAt(i) != BLANK || !isFormattedBlank(froms, i)) { 26 | filteredFroms.append(froms.charAt(i)); 27 | } 28 | } 29 | 30 | for (String from : filteredFroms.toString().split(",")) { 31 | String tableAlias = from.split(" ")[1]; 32 | query = query.replaceAll(tableAlias + ".", "").replaceAll(tableAlias, ""); 33 | } 34 | } 35 | 36 | return query; 37 | } 38 | 39 | public boolean isFormattedBlank(String string, int index) { 40 | try { 41 | int prevIndex = index - 1; 42 | int nextIndex = index + 1; 43 | 44 | char prevChar = string.charAt(prevIndex); 45 | char currentChar = string.charAt(index); 46 | char nextChar = string.charAt(nextIndex); 47 | 48 | if (prevChar != BLANK && currentChar == BLANK && nextChar != BLANK) 49 | return false; 50 | 51 | } catch (Exception e) { 52 | // ignore 53 | } 54 | 55 | return true; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/db/utils/QueryNormalizer.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.db.utils; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.sql.Timestamp; 6 | import java.text.SimpleDateFormat; 7 | import java.util.Date; 8 | import java.util.List; 9 | 10 | public class QueryNormalizer { 11 | private static final String DATE_FORMAT = "yyyy/MM/dd HH:mm:ss.SSS"; 12 | private Boolean dumpBooleanAsTrueFalse = false; 13 | 14 | public String format(String databaseType, String query, List parameters) { 15 | if (StringUtils.isEmpty(query) || query.indexOf('?') < 0) { 16 | return query; 17 | } 18 | 19 | StringBuilder builder = new StringBuilder(query.length() << 1); 20 | 21 | int index = 1; 22 | int queryPrev = trim(query, 0); 23 | 24 | for (int queryCurrent = queryPrev; queryCurrent < query.length(); ) { 25 | if (parameters != null && query.charAt(queryCurrent) == '?') { 26 | builder.append(query.substring(queryPrev, queryCurrent)); 27 | builder.append(parameters.size() > index ? convert(databaseType, parameters.get(index)) : null); 28 | queryPrev = ++queryCurrent; 29 | index++; 30 | continue; 31 | } 32 | 33 | if (query.charAt(queryCurrent) == '\t') { 34 | builder.append(query.substring(queryPrev, queryCurrent)); 35 | builder.append('\n').append(" "); 36 | 37 | queryPrev = queryCurrent += trim(query, queryCurrent); 38 | continue; 39 | } 40 | 41 | if (query.charAt(queryCurrent) == '\n') { 42 | builder.append(query.substring(queryPrev, queryCurrent + 1)); 43 | builder.append(" "); 44 | 45 | queryPrev = queryCurrent += trim(query, queryCurrent); 46 | continue; 47 | } 48 | 49 | queryCurrent++; 50 | } 51 | 52 | if (queryPrev < query.length()) { 53 | builder.append(query.substring(queryPrev)); 54 | } 55 | 56 | return builder.toString(); 57 | } 58 | 59 | private int trim(String query, int queryCurrent) { 60 | int trim = 0; 61 | 62 | for (; query.length() > queryCurrent + trim && query.charAt(queryCurrent + trim) < ' '; trim++) { 63 | } 64 | 65 | return trim; 66 | } 67 | 68 | private String convert(String databaseType, Object parameter) { 69 | if (parameter == null) { 70 | return "n"; 71 | } 72 | 73 | if (parameter instanceof String) { 74 | return "'" + parameter + "'"; 75 | } 76 | 77 | if (parameter instanceof Boolean) { 78 | return dumpBooleanAsTrueFalse ? (Boolean) parameter ? "true" : "false" : (Boolean) parameter ? "1" : "0"; 79 | } 80 | 81 | if ("oracle".equals(databaseType)) { 82 | if (parameter instanceof Timestamp) { 83 | return "to_timestamp('" + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS").format(parameter) + "', 'yyyy/mm/dd hh24:mi:ss.ff3')"; 84 | } 85 | 86 | if (parameter instanceof Date) { 87 | return "to_date('" + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(parameter) + "', 'yyyy/mm/dd hh24:mi:ss')"; 88 | } 89 | } else { 90 | if (parameter instanceof Date) { 91 | return "'" + new SimpleDateFormat(DATE_FORMAT).format(parameter) + "'"; 92 | } 93 | } 94 | 95 | return String.valueOf(parameter); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/db/utils/SqlFormatter.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.db.utils; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.HashSet; 7 | import java.util.LinkedList; 8 | import java.util.Set; 9 | import java.util.StringTokenizer; 10 | 11 | public class SqlFormatter { 12 | public static final String WHITESPACE = " \n\r\f\t"; 13 | 14 | private static final Set BEGIN_CLAUSES = new HashSet<>(); 15 | private static final Set END_CLAUSES = new HashSet<>(); 16 | private static final Set LOGICAL = new HashSet<>(); 17 | private static final Set QUANTIFIERS = new HashSet<>(); 18 | private static final Set DML = new HashSet<>(); 19 | private static final Set MISC = new HashSet<>(); 20 | 21 | private final Logger logger = LoggerFactory.getLogger(SqlFormatter.class); 22 | 23 | static { 24 | BEGIN_CLAUSES.add("left"); 25 | BEGIN_CLAUSES.add("right"); 26 | BEGIN_CLAUSES.add("inner"); 27 | BEGIN_CLAUSES.add("outer"); 28 | BEGIN_CLAUSES.add("group"); 29 | BEGIN_CLAUSES.add("order"); 30 | 31 | END_CLAUSES.add("where"); 32 | END_CLAUSES.add("set"); 33 | END_CLAUSES.add("having"); 34 | END_CLAUSES.add("join"); 35 | END_CLAUSES.add("from"); 36 | END_CLAUSES.add("by"); 37 | END_CLAUSES.add("join"); 38 | END_CLAUSES.add("into"); 39 | END_CLAUSES.add("union"); 40 | 41 | LOGICAL.add("and"); 42 | LOGICAL.add("or"); 43 | LOGICAL.add("when"); 44 | LOGICAL.add("else"); 45 | LOGICAL.add("end"); 46 | 47 | QUANTIFIERS.add("in"); 48 | QUANTIFIERS.add("all"); 49 | QUANTIFIERS.add("exists"); 50 | QUANTIFIERS.add("some"); 51 | QUANTIFIERS.add("any"); 52 | 53 | DML.add("insert"); 54 | DML.add("update"); 55 | DML.add("delete"); 56 | 57 | MISC.add("select"); 58 | MISC.add("on"); 59 | } 60 | 61 | static final String INDENT_STRING = " "; 62 | static final String INITIAL = "\n "; 63 | 64 | public String format(String source) { 65 | if (source == null) { 66 | return null; 67 | } 68 | 69 | try { 70 | FormatProcess formatProcess = new FormatProcess(source); 71 | return formatProcess.perform(); 72 | } catch (Exception except) { 73 | logger.debug(except.getMessage(), except); 74 | } 75 | 76 | return source; 77 | } 78 | 79 | private static class FormatProcess { 80 | boolean beginLine = true; 81 | boolean afterBeginBeforeEnd = false; 82 | boolean afterByOrSetOrFromOrSelect = false; 83 | // boolean afterValues = false; 84 | boolean afterOn = false; 85 | boolean afterBetween = false; 86 | boolean afterInsert = false; 87 | int inFunction = 0; 88 | int parensSinceSelect = 0; 89 | private LinkedList parenCounts = new LinkedList(); 90 | private LinkedList afterByOrFromOrSelects = new LinkedList(); 91 | 92 | int indent = 1; 93 | 94 | StringBuffer result = new StringBuffer(); 95 | StringTokenizer tokens; 96 | String lastToken; 97 | String token; 98 | String lcToken; 99 | 100 | public FormatProcess(String sql) { 101 | tokens = new StringTokenizer(sql, "()+*/-=<>'`\"[]," + WHITESPACE, true); 102 | } 103 | 104 | public String perform() { 105 | result.append(INITIAL); 106 | 107 | while (tokens.hasMoreTokens()) { 108 | token = tokens.nextToken(); 109 | lcToken = token.toLowerCase(); 110 | 111 | if ("'".equals(token)) { 112 | String tmp; 113 | do { 114 | tmp = tokens.nextToken(); 115 | token += tmp; 116 | } while (!"'".equals(tmp) && tokens.hasMoreTokens()); // cannot 117 | // handle 118 | // single 119 | // quotes 120 | } else if ("\"".equals(token)) { 121 | String tmp; 122 | do { 123 | tmp = tokens.nextToken(); 124 | token += tmp; 125 | } while (!"\"".equals(tmp)); 126 | } 127 | 128 | if (afterByOrSetOrFromOrSelect && ",".equals(token)) { 129 | commaAfterByOrFromOrSelect(); 130 | } else if (afterOn && ",".equals(token)) { 131 | commaAfterOn(); 132 | } else if ("(".equals(token)) { 133 | openParen(); 134 | } else if (")".equals(token)) { 135 | closeParen(); 136 | } else if (BEGIN_CLAUSES.contains(lcToken)) { 137 | beginNewClause(); 138 | } else if (END_CLAUSES.contains(lcToken)) { 139 | endNewClause(); 140 | } else if ("select".equals(lcToken)) { 141 | select(); 142 | } else if (DML.contains(lcToken)) { 143 | updateOrInsertOrDelete(); 144 | } else if ("values".equals(lcToken)) { 145 | values(); 146 | } else if ("on".equals(lcToken)) { 147 | on(); 148 | } else if (afterBetween && lcToken.equals("and")) { 149 | misc(); 150 | afterBetween = false; 151 | } else if (LOGICAL.contains(lcToken)) { 152 | logical(); 153 | } else if (isWhitespace(token)) { 154 | white(); 155 | } else { 156 | misc(); 157 | } 158 | 159 | if (!isWhitespace(token)) { 160 | lastToken = lcToken; 161 | } 162 | } 163 | 164 | return result.toString(); 165 | } 166 | 167 | private void commaAfterOn() { 168 | out(); 169 | indent--; 170 | newline(); 171 | afterOn = false; 172 | afterByOrSetOrFromOrSelect = true; 173 | } 174 | 175 | private void commaAfterByOrFromOrSelect() { 176 | out(); 177 | newline(); 178 | } 179 | 180 | private void logical() { 181 | if ("end".equals(lcToken)) { 182 | indent--; 183 | } 184 | newline(); 185 | out(); 186 | beginLine = false; 187 | } 188 | 189 | private void on() { 190 | indent++; 191 | afterOn = true; 192 | newline(); 193 | out(); 194 | beginLine = false; 195 | } 196 | 197 | private void misc() { 198 | out(); 199 | if ("between".equals(lcToken)) { 200 | afterBetween = true; 201 | } 202 | if (afterInsert) { 203 | newline(); 204 | afterInsert = false; 205 | } else { 206 | beginLine = false; 207 | if ("case".equals(lcToken)) { 208 | indent++; 209 | } 210 | } 211 | } 212 | 213 | private void white() { 214 | if (!beginLine) { 215 | result.append(" "); 216 | } 217 | } 218 | 219 | private void updateOrInsertOrDelete() { 220 | out(); 221 | indent++; 222 | beginLine = false; 223 | if ("update".equals(lcToken)) { 224 | newline(); 225 | } 226 | if ("insert".equals(lcToken)) { 227 | afterInsert = true; 228 | } 229 | } 230 | 231 | private void select() { 232 | out(); 233 | indent++; 234 | newline(); 235 | parenCounts.addLast(new Integer(parensSinceSelect)); 236 | afterByOrFromOrSelects.addLast(Boolean.valueOf(afterByOrSetOrFromOrSelect)); 237 | parensSinceSelect = 0; 238 | afterByOrSetOrFromOrSelect = true; 239 | } 240 | 241 | private void out() { 242 | result.append(token); 243 | } 244 | 245 | private void endNewClause() { 246 | if (!afterBeginBeforeEnd) { 247 | indent--; 248 | if (afterOn) { 249 | indent--; 250 | afterOn = false; 251 | } 252 | newline(); 253 | } 254 | out(); 255 | if (!"union".equals(lcToken)) { 256 | indent++; 257 | } 258 | newline(); 259 | afterBeginBeforeEnd = false; 260 | afterByOrSetOrFromOrSelect = "by".equals(lcToken) || "set".equals(lcToken) || "from".equals(lcToken); 261 | } 262 | 263 | private void beginNewClause() { 264 | if (!afterBeginBeforeEnd) { 265 | if (afterOn) { 266 | indent--; 267 | afterOn = false; 268 | } 269 | indent--; 270 | newline(); 271 | } 272 | out(); 273 | beginLine = false; 274 | afterBeginBeforeEnd = true; 275 | } 276 | 277 | private void values() { 278 | indent--; 279 | newline(); 280 | out(); 281 | indent++; 282 | newline(); 283 | // afterValues = true; 284 | } 285 | 286 | private void closeParen() { 287 | parensSinceSelect--; 288 | if (parensSinceSelect < 0) { 289 | indent--; 290 | parensSinceSelect = (parenCounts.removeLast()).intValue(); 291 | afterByOrSetOrFromOrSelect = (afterByOrFromOrSelects.removeLast()).booleanValue(); 292 | } 293 | if (inFunction > 0) { 294 | inFunction--; 295 | out(); 296 | } else { 297 | if (!afterByOrSetOrFromOrSelect) { 298 | indent--; 299 | newline(); 300 | } 301 | out(); 302 | } 303 | beginLine = false; 304 | } 305 | 306 | private void openParen() { 307 | if (isFunctionName(lastToken) || inFunction > 0) { 308 | inFunction++; 309 | } 310 | beginLine = false; 311 | if (inFunction > 0) { 312 | out(); 313 | } else { 314 | out(); 315 | if (!afterByOrSetOrFromOrSelect) { 316 | indent++; 317 | newline(); 318 | beginLine = true; 319 | } 320 | } 321 | parensSinceSelect++; 322 | } 323 | 324 | private static boolean isFunctionName(String tok) { 325 | final char begin = tok.charAt(0); 326 | final boolean isIdentifier = Character.isJavaIdentifierStart(begin) || '"' == begin; 327 | return isIdentifier && !LOGICAL.contains(tok) && !END_CLAUSES.contains(tok) && !QUANTIFIERS.contains(tok) && !DML.contains(tok) && !MISC.contains(tok); 328 | } 329 | 330 | private static boolean isWhitespace(String token) { 331 | return WHITESPACE.indexOf(token) >= 0; 332 | } 333 | 334 | private void newline() { 335 | result.append("\n"); 336 | for (int i = 0; i < indent; i++) { 337 | result.append(INDENT_STRING); 338 | } 339 | beginLine = true; 340 | } 341 | } 342 | 343 | } 344 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/db/utils/SqlMonitoringLogUtil.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.db.utils; 2 | 3 | import io.brant.example.jdbc.db.monitor.sql.SqlExecutionInfo; 4 | import org.apache.commons.lang3.SystemUtils; 5 | 6 | import java.util.List; 7 | 8 | public class SqlMonitoringLogUtil { 9 | private List sqlExecutionInfoList; 10 | 11 | public SqlMonitoringLogUtil(List sqlExecutionInfoList) { 12 | this.sqlExecutionInfoList = sqlExecutionInfoList; 13 | } 14 | 15 | public void saveSqlMonitoringInfo() { 16 | if (this.sqlExecutionInfoList == null) { 17 | return; 18 | } 19 | StringBuilder stringBuilder = new StringBuilder("[SqlMonitoringInfo]"); 20 | stringBuilder.append(SystemUtils.LINE_SEPARATOR); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/entity/User.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.entity; 2 | 3 | import org.hibernate.annotations.DynamicInsert; 4 | import org.hibernate.annotations.DynamicUpdate; 5 | 6 | import javax.persistence.*; 7 | import java.io.Serializable; 8 | 9 | @Entity 10 | @Table(name = "users") 11 | @DynamicInsert 12 | @DynamicUpdate 13 | public class User implements Serializable { 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.IDENTITY) 17 | private Long id; 18 | 19 | @Column(name="first_name", length = 100) 20 | private String firstName; 21 | 22 | @Column(name = "last_name", length = 100) 23 | private String lastName; 24 | 25 | @Column(name = "email", length = 100) 26 | private String email; 27 | 28 | @Column(name = "password", length = 255) 29 | private String password; 30 | 31 | public Long getId() { 32 | return id; 33 | } 34 | 35 | public void setId(Long id) { 36 | this.id = id; 37 | } 38 | 39 | public String getFirstName() { 40 | return firstName; 41 | } 42 | 43 | public void setFirstName(String firstName) { 44 | this.firstName = firstName; 45 | } 46 | 47 | public String getLastName() { 48 | return lastName; 49 | } 50 | 51 | public void setLastName(String lastName) { 52 | this.lastName = lastName; 53 | } 54 | 55 | public String getEmail() { 56 | return email; 57 | } 58 | 59 | public void setEmail(String email) { 60 | this.email = email; 61 | } 62 | 63 | public String getPassword() { 64 | return password; 65 | } 66 | 67 | public void setPassword(String password) { 68 | this.password = password; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/entity/UserRepository.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.entity; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | @Repository 7 | public interface UserRepository extends CrudRepository { 8 | 9 | User findByEmail(String email); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/brant/example/jdbc/entity/UserService.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.entity; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | 6 | @Service 7 | public class UserService { 8 | 9 | @Autowired 10 | private UserRepository userRepository; 11 | 12 | 13 | public User create(User user) { 14 | userRepository.save(user); 15 | return user; 16 | } 17 | 18 | public boolean existUser(String email) { 19 | User user = findByEmail(email); 20 | return user != null ? true : false; 21 | } 22 | 23 | public User findByEmail(String email) { 24 | return userRepository.findByEmail(email); 25 | } 26 | 27 | public User findById(Long id) { 28 | return userRepository.findOne(id); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | dataSource.custom.databaseType=h2 2 | dataSource.custom.username=sa 3 | dataSource.custom.password= 4 | dataSource.custom.url=jdbc:h2:mem:test 5 | dataSource.custom.driverClassName=org.h2.Driver 6 | dataSource.custom.initialSize=5 7 | dataSource.custom.minIdle=10 8 | dataSource.custom.maxIdle=-1 9 | dataSource.custom.maxWait=3000 10 | dataSource.custom.maxActive=10 11 | dataSource.custom.testOnBorrow=false 12 | dataSource.custom.testOnReturn=false 13 | dataSource.custom.testWhileIdle=true 14 | dataSource.custom.timeBetweenEvictionRunsMillis=600000 15 | dataSource.custom.minEvictableIdleTimeMillis=-1 16 | dataSource.custom.softMinEvictableIdleTimeMillis=300000 17 | dataSource.custom.accessToUnderlyingConnectionAllowed=true 18 | dataSource.custom.dataSourceId=mainDataSource 19 | dataSource.custom.sqlLogging=true 20 | dataSource.custom.slowQueryThreshold=1000 21 | dataSource.custom.queryTimeout=300000 22 | dataSource.custom.queryFormatting=true 23 | dataSource.custom.hibernateQueryFormatting=true 24 | dataSource.custom.connectionInitSql=SELECT 1 25 | dataSource.custom.validationQuery=SELECT 1 26 | 27 | spring.jpa.hibernate.ddl-auto=create 28 | spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy 29 | spring.jpa.database=H2 30 | spring.jpa.show-sql=false -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %c:%M:%L -%X{currentUser}%X{requestParams} %m%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/resources/templates/view.ftl: -------------------------------------------------------------------------------- 1 | <#import "/spring.ftl" as spring /> 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | <#if sqlExecutionInfos??> 27 | <#list sqlExecutionInfos as item> 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | <#else> 45 | 46 | 47 | 48 | 49 | 50 |
SQLmin(ms)max(ms)average(ms)total(ms)total ExecutionslowQueries(>${slowQueryThreshold}ms)exceptionstimeoutlast execution timelongest execution time
30 |
${item.getFormattedSql(dataSourceConfiguration.isHibernateQueryFormatting())?string}
31 |
${item.min?c}${item.max?c}${item.average?c}${item.total?c}${item.count?c}${item.slowQueryCount?c}${item.exceptionCount?c}${item.queryTimeoutCount?c}${item.lastQueryDate?datetime}${item.longestQueryDateTime?datetime}
No data
51 |
52 | 53 | -------------------------------------------------------------------------------- /src/test/java/io/brant/example/jdbc/entity/UserServiceTest.java: -------------------------------------------------------------------------------- 1 | package io.brant.example.jdbc.entity; 2 | 3 | import io.brant.example.jdbc.ApplicationInitializer; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.SpringApplicationConfiguration; 8 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9 | import org.springframework.transaction.annotation.EnableTransactionManagement; 10 | 11 | @SuppressWarnings("SpringJavaAutowiredMembersInspection") 12 | @RunWith(SpringJUnit4ClassRunner.class) 13 | @SpringApplicationConfiguration(classes = {ApplicationInitializer.class}) 14 | @EnableTransactionManagement 15 | public class UserServiceTest { 16 | 17 | @Autowired 18 | private UserService userService; 19 | 20 | 21 | @Test 22 | public void createTest() { 23 | User user = new User(); 24 | user.setEmail("dlstj3039@gmail.com"); 25 | user.setFirstName("Brant"); 26 | user.setLastName("Hwang"); 27 | user.setPassword("1234"); 28 | 29 | userService.create(user); 30 | } 31 | 32 | @Test 33 | public void selectTest() { 34 | User user = userService.findByEmail("dlstj3039@gmail.com"); 35 | } 36 | } --------------------------------------------------------------------------------