001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jdbc.core.support;
018
019import java.sql.ResultSet;
020import java.sql.SQLException;
021import java.util.Properties;
022import javax.sql.DataSource;
023
024import org.springframework.beans.factory.support.BeanDefinitionRegistry;
025import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;
026import org.springframework.jdbc.core.JdbcTemplate;
027import org.springframework.jdbc.core.RowCallbackHandler;
028import org.springframework.util.Assert;
029
030/**
031 * Bean definition reader that reads values from a database table,
032 * based on a given SQL statement.
033 *
034 * <p>Expects columns for bean name, property name and value as String.
035 * Formats for each are identical to the properties format recognized
036 * by PropertiesBeanDefinitionReader.
037 *
038 * <p><b>NOTE:</b> This is mainly intended as an example for a custom
039 * JDBC-based bean definition reader. It does not aim to offer
040 * comprehensive functionality.
041 *
042 * @author Rod Johnson
043 * @author Juergen Hoeller
044 * @see #loadBeanDefinitions
045 * @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
046 */
047public class JdbcBeanDefinitionReader {
048
049        private final PropertiesBeanDefinitionReader propReader;
050
051        private JdbcTemplate jdbcTemplate;
052
053
054        /**
055         * Create a new JdbcBeanDefinitionReader for the given bean factory,
056         * using a default PropertiesBeanDefinitionReader underneath.
057         * <p>DataSource or JdbcTemplate still need to be set.
058         * @see #setDataSource
059         * @see #setJdbcTemplate
060         */
061        public JdbcBeanDefinitionReader(BeanDefinitionRegistry beanFactory) {
062                this.propReader = new PropertiesBeanDefinitionReader(beanFactory);
063        }
064
065        /**
066         * Create a new JdbcBeanDefinitionReader that delegates to the
067         * given PropertiesBeanDefinitionReader underneath.
068         * <p>DataSource or JdbcTemplate still need to be set.
069         * @see #setDataSource
070         * @see #setJdbcTemplate
071         */
072        public JdbcBeanDefinitionReader(PropertiesBeanDefinitionReader beanDefinitionReader) {
073                Assert.notNull(beanDefinitionReader, "Bean definition reader must not be null");
074                this.propReader = beanDefinitionReader;
075        }
076
077
078        /**
079         * Set the DataSource to use to obtain database connections.
080         * Will implicitly create a new JdbcTemplate with the given DataSource.
081         */
082        public void setDataSource(DataSource dataSource) {
083                this.jdbcTemplate = new JdbcTemplate(dataSource);
084        }
085
086        /**
087         * Set the JdbcTemplate to be used by this bean factory.
088         * Contains settings for DataSource, SQLExceptionTranslator, etc.
089         */
090        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
091                Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null");
092                this.jdbcTemplate = jdbcTemplate;
093        }
094
095
096        /**
097         * Load bean definitions from the database via the given SQL string.
098         * @param sql SQL query to use for loading bean definitions.
099         * The first three columns must be bean name, property name and value.
100         * Any join and any other columns are permitted: e.g.
101         * {@code SELECT BEAN_NAME, PROPERTY, VALUE FROM CONFIG WHERE CONFIG.APP_ID = 1}
102         * It's also possible to perform a join. Column names are not significant --
103         * only the ordering of these first three columns.
104         */
105        public void loadBeanDefinitions(String sql) {
106                Assert.notNull(this.jdbcTemplate, "Not fully configured - specify DataSource or JdbcTemplate");
107                final Properties props = new Properties();
108                this.jdbcTemplate.query(sql, new RowCallbackHandler() {
109                        @Override
110                        public void processRow(ResultSet rs) throws SQLException {
111                                String beanName = rs.getString(1);
112                                String property = rs.getString(2);
113                                String value = rs.getString(3);
114                                // Make a properties entry by combining bean name and property.
115                                props.setProperty(beanName + '.' + property, value);
116                        }
117                });
118                this.propReader.registerBeanDefinitions(props);
119        }
120
121}