简体   繁体   English

如何从 spring boot jdbc 存储库中的属性或 yml 文件存储和读取 SQL 查询?

[英]How to store and read SQL queries from properties or yml file in spring boot jdbc repository?

I am using spring boot and spring jdbc template.我正在使用 spring boot 和 spring jdbc 模板。 I want to externalize the SQL queries in either properties or yml file.我想在属性或 yml 文件中外部化 SQL 查询。 I dont want to store the SQL queries in the java repositories classes.我不想将 SQL 查询存储在 java 存储库类中。

What is the best way to handle this case?处理这种情况的最佳方法是什么?

This is how my repository class looks right now.这就是我的存储库类现在的样子。

@Repository
public class UserRepositoryImpl extends BaseRepository implements UserRepository {

    @Override
    public List<User> findAll(){
        String sqlQuery = "SELECT * FROM users";
        return jdbcTemplate.query(sqlQuery,  userMapper);
    }

    @Override
    public User findById(Long userId){
        String sqlQuery = "SELECT * FROM users WHERE id = :userId";
        Map<String, String> namedParameters = new HashMap<String, String>();
        namedParameters.put("userId", String.valueOf(userId));
        return jdbcTemplate.queryForObject(sqlQuery, namedParameters, userMapper);
    }

I know this doesn't directly address how your ask regarding properties files or yml, but I interpret your question generally as asking about the best way to manage sql statements in a project.我知道这并没有直接解决您对属性文件或 yml 的询问,但我通常将您的问题解释为询问在项目中管理 sql 语句的最佳方法。 Having worked on projects with quite a lot of SQL code I've found MyBatis to hold up without too much complaint.在处理了大量 SQL 代码的项目后,我发现 MyBatis 可以承受而没有太多抱怨。 In a nutshell, it already handles externalizing sql to external xml files and can keep the manageability of the sql in the files at a good level as you accumulate more sql.简而言之,它已经可以将sql外部化到外部xml文件中,并且可以在您积累更多sql时将文件中的sql的可管理性保持在一个良好的水平。

To set it up you basically need to configure the beans and create two mybatis xml files along with a java interface for the repository.要设置它,您基本上需要配置 bean 并创建两个 mybatis xml 文件以及存储库的 java 接口。 Taking your example, here's the mybatis for the user repository:以您的示例为例,这是用户存储库的 mybatis:

public class User {

  private Long id;
  private String name;

 ...
}

public interface UserRepository {

  List<User> findAll();

  User findById( @Param( "id" ) Long userId );

}

The @Param will map the 'id' value to the #{id} expression in the SQL @Param 会将 'id' 值映射到 SQL 中的 #{id} 表达式

META-INF/repo/sql/userMapper.xml: META-INF/repo/sql/userMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bushcoder.so.app.user.UserRepository">

    <resultMap id="user" type="com.bushcoder.so.app.user.User">
        <id property="id" column="userId"/>
        <result property="name" column="name"/>
    </resultMap>

    <select id="findAll" resultMap="user">
        SELECT id, name FROM user
    </select>

    <select id="findById" parameterType="long" resultMap="user">
        SELECT id, name FROM user WHERE id = #{id}
    </select>

</mapper>

Note: #{id} will be supplied the value passed in via the call to userRepository.findById注意:#{id} 将提供通过调用 userRepository.findById 传入的值

META-INF/repo/sql/sqlmap-config.xml: META-INF/repo/sql/sqlmap-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//www.mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" >
<configuration>

    <mappers>
        <mapper resource="META-INF/repo/sql/userMapper.xml"/>
    </mappers>

</configuration>

The 'META-INF/repo/sql/sqlmap-config.xml' path will be used in the Java Config to setup the beans required by mybatis. 'META-INF/repo/sql/sqlmap-config.xml' 路径将在 Java Config 中用于设置 mybatis 所需的 bean。 So for the configuration you'll need 4 beans: sqlSessionFactory, sqlSessionTemplate, dataSource and the userRepository.因此,对于配置,您需要 4 个 bean:s​​qlSessionFactory、sqlSessionTemplate、dataSource 和 userRepository。 These need to be somewhere in a configuration class for Spring to process.这些需要在配置类中的某个地方供 Spring 处理。

  @Bean
  public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
    sqlSessionFactory.setDataSource(dataSource());
    sqlSessionFactory.setConfigLocation( new ClassPathResource( "META-INF/repo/sql/sqlmap-config.xml" ) );
    return sqlSessionFactory.getObject();
  }

  @Bean
  public SqlSessionTemplate sqlSessionTemplate() throws Exception {
    return new SqlSessionTemplate(sqlSessionFactory());
  }

  @Bean
  public DataSource dataSource() {
    EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
    EmbeddedDatabase db = builder
        .setType( EmbeddedDatabaseType.H2)
        .addScript("META-INF/repo/db/ddl/create-database-script.sql")
        .addScript("META-INF/repo/db/dml/database-seeder-script.sql")
        .build();
    return db;
  }

  @Bean
  public UserRepository userRepository() throws Exception {
    return sqlSessionTemplate().getMapper( UserRepository.class );
  }

In my prototype project I went to the H2 database and am using the EmbeddedDatabaseBuilder to take care of the schema and seed data.在我的原型项目中,我访问了 H2 数据库并使用 EmbeddedDatabaseBuilder 来处理模式和种子数据。

META-INF/repo/db/ddl/create-database-script.sql: META-INF/repo/db/ddl/create-database-script.sql:

CREATE TABLE if NOT EXISTS user (
  id    INTEGER PRIMARY KEY,
  name  VARCHAR(30)
);

META-INF/repo/db/dml/database-seeder-script.sql: META-INF/repo/db/dml/database-seeder-script.sql:

INSERT INTO user (id, name) VALUES (1, 'BOB');
INSERT INTO user (id, name) VALUES (2, 'LARRY');
INSERT INTO user (id, name) VALUES (3, 'FRANK');
INSERT INTO user (id, name) VALUES (4, 'CHARLIE');
INSERT INTO user (id, name) VALUES (5, 'GARRY');

More than likely you'll wire the repository into a service.您很可能会将存储库连接到服务中。 Might look something like this:可能看起来像这样:

public interface UserService {

  List<User> findAll();

  User findById(Long userId);

}

@Service
public class UserServiceImpl implements UserService {

  @Inject
  private UserRepository userRepository;

  @Override
  public List<User> findAll() {
    return userRepository.findAll();
  }

  @Override
  public User findById( Long userId ) {
    return userRepository.findById( userId );
  }
}

The calling code could be like this:调用代码可能是这样的:

@SpringBootApplication
@Import ( AppConfig.class )
public class MybatisConfigExampleApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run( MybatisConfigExampleApplication.class, args );

        final UserService users = ( UserService ) context.getBean( "userServiceImpl" );

        final List<User> allUsers = users.findAll();
        System.out.println( "allUsers = " + allUsers );

        final User userById_5 = users.findById( 5L );
        System.out.println( "userById_5 = " + userById_5 );
    }
}

Now, as you start to accumulate more sql, you would create a new repository interface, its matching mapper file, link the mapper xml file via the sqlmap-config xml file by adding a new <mapper> element for it, and then add the new repository as a bean in Spring's config.现在,当您开始积累更多 sql 时,您将创建一个新的存储库接口及其匹配的映射器文件,通过 sqlmap-config xml 文件通过为它添加一个新的<mapper>元素来链接映射器 xml 文件,然后添加新存储库作为 Spring 配置中的 bean。 Moreover, and I didn't show it hear, if userMapper.xml starts to get too large and cumbersome you can break it apart into smaller files and still keep the UserRepository interface.此外,我没有听到,如果 userMapper.xml 开始变得太大和太麻烦,您可以将它分解成更小的文件,并仍然保留 UserRepository 接口。

I handled as follows:我是这样处理的:

I have a @Configuration class which creates jdbcTemplate beans so I add an another bean with class of StringBuilder to hold query from .sql file.我有一个创建 jdbcTemplate bean 的@Configuration类,所以我添加了另一个带有StringBuilder类的 bean 来保存来自 .sql 文件的查询。 Here is my configuration:这是我的配置:

@Configuration
public class DBManager {

    private static final Logger logger = LoggerFactory.getLogger(DBManager.class);

    @Autowired
    PropertiesUtils propertiesUtils;

    @Bean(name = "targetJdbcTemplate")
    public JdbcTemplate targetJdbcTemplate() throws SQLException {
        Environment environment = propertiesUtils.getEnvironment();
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl(environment.getProperty("db.target.url"));
        dataSource.setUsername(environment.getProperty("db.target.username"));
        dataSource.setPassword(environment.getProperty("db.target.password"));

        return new JdbcTemplate(dataSource);
    }

    @Bean(name = "targetQueryTemplate")
    public StringBuilder targetQueryTemplate() {
        return propertiesUtils.getSQLQueryFromFile(DBDirection.TARGET_DB);
    }
}

PropertiesUtil looks like: PropertiesUtil看起来像:

@Configuration
@PropertySource(value={"classpath:app.properties"})
public class PropertiesUtils {

    private static final Logger logger = LoggerFactory.getLogger(PropertiesUtils.class);

    @Resource
    private Environment environment;

    public Environment getEnvironment() {
        return environment;
    }

    /**
     * to get sql query from .sql file
     * @param dbDirection which db's query is needed
     * @return a StringBuilder object which holds needed sql query
     */
    public StringBuilder getSQLQueryFromFile(DBDirection dbDirection) {
        String filePath = null;
        StringBuilder sql = null;
        BufferedReader br = null;
        InputStreamReader input = null;
        try {
            if (dbDirection == DBDirection.SOURCE_DB)
                filePath = this.environment.getProperty("db.source.query.file");
            else if (dbDirection == DBDirection.TARGET_DB){
                filePath = this.environment.getProperty("db.target.query.file");

            if(filePath == null || filePath.equals("")) {
                logger.error("filePath cannot be null or empty");
                return sql;
            }

            InputStream in = PropertiesUtils.class.getClassLoader().getResourceAsStream(filePath);
            input = new InputStreamReader(in);
            br = new BufferedReader(input);
            String str;
            sql = new StringBuilder("");
            while ((str = br.readLine()) != null) {
                sql.append(str);
            }
        } catch (IOException e) {
            logger.error("Failed to read query from file", e);
        } finally {
            try {
                if(br != null)
                    br.close();
                if(input != null)
                    input.close();
            } catch (IOException e) {
                logger.error("Failed to close reader", e);
            }
        }
        return sql;
    }
}

app.properties holds .sql file's path. app.properties保存 .sql 文件的路径。 getSQLQueryFromFile reads the file once while context initializing. getSQLQueryFromFile 在上下文初始化时读取文件一次。

Then I wire the query holder bean (targetQueryTemplate) to my repo that's it.然后我将查询持有者 bean (targetQueryTemplate) 连接到我的仓库,就是这样。 Here is my repo:这是我的回购:

@Repository
public class TargetRepository implements ITargetRepository {

    private static final Logger logger = LoggerFactory.getLogger(TargetRepository.class);
    private static final String DEFAULT_DATE_FORMAT = "yyyyMMddHHmmss";

    @Autowired
    @Qualifier("targetJdbcTemplate")
    private JdbcTemplate targetJdbcTemplate;

    @Autowired
    @Qualifier("targetQueryTemplate")
    private StringBuilder targetQueryTemplate;

    @Override
    public void testConnection() {
        targetJdbcTemplate.execute("select 1 from dual");
    }

    @Override
    public int[] insert(final ArrayList<Object> list) {
        return targetJdbcTemplate.batchUpdate(this.targetQueryTemplate.toString(), new BatchPreparedStatementSetter() {

            @Override
            public void setValues(PreparedStatement preparedStatement, int i) throws SQLException {
                // batch adding
            }

            @Override
            public int getBatchSize() {
                return determineBatchSize(list);
            }
        });
    }
}

Hope this helps!希望这有帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM