001/*
002 * Copyright 2002-2018 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.util.Properties;
020
021import javax.sql.DataSource;
022
023import org.springframework.beans.factory.support.BeanDefinitionRegistry;
024import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;
025import org.springframework.jdbc.core.JdbcTemplate;
026import org.springframework.lang.Nullable;
027import org.springframework.util.Assert;
028
029/**
030 * Bean definition reader that reads values from a database table,
031 * based on a given SQL statement.
032 *
033 * <p>Expects columns for bean name, property name and value as String.
034 * Formats for each are identical to the properties format recognized
035 * by PropertiesBeanDefinitionReader.
036 *
037 * <p><b>NOTE:</b> This is mainly intended as an example for a custom
038 * JDBC-based bean definition reader. It does not aim to offer
039 * comprehensive functionality.
040 *
041 * @author Rod Johnson
042 * @author Juergen Hoeller
043 * @see #loadBeanDefinitions
044 * @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
045 */
046public class JdbcBeanDefinitionReader {
047
048        private final PropertiesBeanDefinitionReader propReader;
049
050        @Nullable
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 the 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, rs -> {
109                        String beanName = rs.getString(1);
110                        String property = rs.getString(2);
111                        String value = rs.getString(3);
112                        // Make a properties entry by combining bean name and property.
113                        props.setProperty(beanName + '.' + property, value);
114                });
115                this.propReader.registerBeanDefinitions(props);
116        }
117
118}