• Spring Boot实战:数据库操作


      上篇文章中已经通过一个简单的HelloWorld程序讲解了Spring boot的基本原理和使用。本文主要讲解如何通过spring boot来访问数据库,本文会演示三种方式来访问数据库,第一种是JdbcTemplate,第二种是JPA,第三种是Mybatis。之前已经提到过,本系列会以一个博客系统作为讲解的基础,所以本文会讲解文章的存储和访问(但不包括文章的详情),因为最终的实现是通过MyBatis来完成的,所以,对于JdbcTemplate和JPA只做简单演示,MyBatis部分会完整实现对文章的增删改查。

    一、准备工作

      在演示这几种方式之前,需要先准备一些东西。第一个就是数据库,本系统是采用MySQL实现的,我们需要先创建一个tb_article的表:

    DROP TABLE IF EXISTS `tb_article`;
    
    CREATE TABLE `tb_article` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `title` varchar(255) NOT NULL DEFAULT '',
      `summary` varchar(1024) NOT NULL DEFAULT '',
      `status` int(11) NOT NULL DEFAULT '0',
      `type` int(11) NOT NULL,
      `user_id` bigint(20) NOT NULL DEFAULT '0',
      `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      `public_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

      后续的演示会对这个表进行增删改查,大家应该会看到这个表里面并没有文章的详情,原因是文章的详情比较长,如果放在这个表里面容易影响查询文章列表的效率,所以文章的详情会单独存在另外的表里面。此外我们需要配置数据库连接池,这里我们使用druid连接池,另外配置文件使用yaml配置,即application.yml(你也可以使用application.properties配置文件,没什么太大的区别,如果对ymal不熟悉,有兴趣也可以查一下,比较简单)。连接池的配置如下:

    spring:
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/blog?useUnicode=true&characterEncoding=UTF-8&useSSL=false
        driverClassName: com.mysql.jdbc.Driver
        username: root
        password: 123456
        type: com.alibaba.druid.pool.DruidDataSource
    

      最后,我们还需要建立与数据库对应的POJO类,代码如下:

    public class Article {
        private Long id;
        private String title;
        private String summary;
        private Date createTime;
        private Date publicTime;
        private Date updateTime;
        private Long userId;
        private Integer status;
    private Integer type;

    }

      好了,需要准备的工作就这些,现在开始实现数据库的操作。

     二、与JdbcTemplate集成

      首先,我们先通过JdbcTemplate来访问数据库,这里只演示数据的插入,上一篇文章中我们已经提到过,Spring boot提供了许多的starter来支撑不同的功能,要支持JdbcTemplate我们只需要引入下面的starter就可以了:

    <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    

      现在我们就可以通过JdbcTemplate来实现数据的插入了:

    public interface ArticleDao {
        Long insertArticle(Article article);
    }
    
    @Repository
    public class ArticleDaoJdbcTemplateImpl implements ArticleDao {
    
        @Autowired
        private NamedParameterJdbcTemplate jdbcTemplate;
    
        @Override
        public Long insertArticle(Article article) {
            String sql = "insert into tb_article(title,summary,user_id,create_time,public_time,update_time,status) " +
                    "values(:title,:summary,:userId,:createTime,:publicTime,:updateTime,:status)";
            Map<String, Object> param = new HashMap<>();
            param.put("title", article.getTitle());
            param.put("summary", article.getSummary());
            param.put("userId", article.getUserId());
            param.put("status", article.getStatus());
            param.put("createTime", article.getCreateTime());
            param.put("publicTime", article.getPublicTime());
            param.put("updateTime", article.getUpdateTime());
            return (long) jdbcTemplate.update(sql, param);
        }
    }
    

      我们通过JUnit来测试上面的代码:

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(classes = Application.class)
    public class ArticleDaoTest {
    
        @Autowired
        private ArticleDao articleDao;
    
        @Test
        public void testInsert() {
            Article article = new Article();
            article.setTitle("测试标题");
            article.setSummary("测试摘要");
            article.setUserId(1L);
            article.setStatus(1);
            article.setCreateTime(new Date());
            article.setUpdateTime(new Date());
            article.setPublicTime(new Date());
            articleDao.insertArticle(article);
        }
    }
    

       要支持上面的测试程序,也需要引入一个starter:

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
     </dependency>
    

      从上面的代码可以看出,其实除了引入jdbc的start之外,基本没有配置,这都是spring boot的自动帮我们完成了配置的过程。上面的代码需要注意的Application类的位置,该类必须位于Dao类的父级的包中,比如这里Dao都位于com.pandy.blog.dao这个包下,现在我们把Application.java这个类从com.pandy.blog这个包移动到com.pandy.blog.app这个包中,则会出现如下错误:

    Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.pandy.blog.dao.ArticleDao' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1493)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
    	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
    	... 28 more
    

      也就是说,找不到ArticleDao的实现,这是什么原因呢?上一篇博文中我们已经看到@SpringBootApplication这个注解继承了@ComponentScan,其默认情况下只会扫描Application类所在的包及子包。因此,对于上面的错误,除了保持Application类在Dao的父包这种方式外,也可以指定扫描的包来解决:

    @SpringBootApplication
    @ComponentScan({"com.pandy.blog"})
    public class Application {
        public static void main(String[] args) throws Exception {
            SpringApplication.run(Application.class, args);
        }
    }
    

        

    三、与JPA集成

      现在我们开始讲解如何通过JPA的方式来实现数据库的操作。还是跟JdbcTemplate类似,首先,我们需要引入对应的starter:

    <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    

      然后我们需要对POJO类增加Entity的注解,并指定表名(如果不指定,默认的表名为article),然后需要指定ID的及其生成策略,这些都是JPA的知识,与Spring boot无关,如果不熟悉的话可以看下JPA的知识点:

    @Entity(name = "tb_article")
    public class Article {
        @Id
        @GeneratedValue
        private Long id;
        private String title;
        private String summary;
        private Date createTime;
        private Date publicTime;
        private Date updateTime;
        private Long userId;
        private Integer status;
    }
    

      最后,我们需要继承JpaRepository这个类,这里我们实现了两个查询方法,第一个是符合JPA命名规范的查询,JPA会自动帮我们完成查询语句的生成,另一种方式是我们自己实现JPQL(JPA支持的一种类SQL的查询)。

    public interface ArticleRepository extends JpaRepository<Article, Long> {
    
        public List<Article> findByUserId(Long userId);
    
        @Query("select art from com.pandy.blog.po.Article art where title=:title")
        public List<Article> queryByTitle(@Param("title") String title);
    }
    

      好了,我们可以再测试一下上面的代码:

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(classes = Application.class)
    public class ArticleRepositoryTest {
        @Autowired
        private ArticleRepository articleRepository;
    
        @Test
        public void testQuery(){
            List<Article> articleList = articleRepository.queryByTitle("测试标题");
            assertTrue(articleList.size()>0);
        }
    }
    

      注意,这里还是存在跟JdbcTemplate类似的问题,需要将Application这个启动类未于Respository和Entity类的父级包中,否则会出现如下错误:

    Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.pandy.blog.dao.ArticleRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1493)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
    	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
    	... 28 more
    

      当然,同样也可以通过注解@EnableJpaRepositories指定扫描的JPA的包,但是还是不行,还会出现如下错误:

    Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.pandy.blog.po.Article
    	at org.hibernate.jpa.internal.metamodel.MetamodelImpl.managedType(MetamodelImpl.java:210)
    	at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.<init>(JpaMetamodelEntityInformation.java:70)
    	at org.springframework.data.jpa.repository.support.JpaEntityInformationSupport.getEntityInformation(JpaEntityInformationSupport.java:68)
    	at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getEntityInformation(JpaRepositoryFactory.java:153)
    	at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:100)
    	at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:82)
    	at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:199)
    	at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.initAndReturn(RepositoryFactoryBeanSupport.java:277)
    	at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:263)
    	at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:101)
    	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
    	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
    	... 39 more
    

      这个错误说明识别不了Entity,所以还需要通过注解@EntityScan来指定Entity的包,最终的配置如下:

    @SpringBootApplication
    @ComponentScan({"com.pandy.blog"})
    @EnableJpaRepositories(basePackages="com.pandy.blog")
    @EntityScan("com.pandy.blog")
    public class Application {
        public static void main(String[] args) throws Exception {
            SpringApplication.run(Application.class, args);
        }
    }
    

    四、与MyBatis集成

      最后,我们再看看如何通过MyBatis来实现数据库的访问。同样我们还是要引入starter:

    <dependency>
          <groupId>org.mybatis.spring.boot</groupId>
          <artifactId>mybatis-spring-boot-starter</artifactId>
          <version>1.1.1</version>
    </dependency>
    

      由于该starter不是spring boot官方提供的,所以版本号于Spring boot不一致,需要手动指定。

      MyBatis一般可以通过XML或者注解的方式来指定操作数据库的SQL,个人比较偏向于XML,所以,本文中也只演示了通过XML的方式来访问数据库。首先,我们需要配置mapper的目录。我们在application.yml中进行配置:

    mybatis:
      config-locations: mybatis/mybatis-config.xml
      mapper-locations: mybatis/mapper/*.xml
      type-aliases-package: com.pandy.blog.po
    

      这里配置主要包括三个部分,一个是mybatis自身的一些配置,例如基本类型的别名。第二个是指定mapper文件的位置,第三个POJO类的别名。这个配置也可以通过 Java configuration来实现,由于篇幅的问题,我这里就不详述了,有兴趣的朋友可以自己实现一下。

      配置完后,我们先编写mapper对应的接口:

    public interface ArticleMapper {
    
        public Long insertArticle(Article article);
    
        public void updateArticle(Article article);
    
        public Article queryById(Long id);
    
        public List<Article> queryArticlesByPage(@Param("article") Article article, @Param("pageSize") int pageSize,
                                                 @Param("offset") int offset);
    
    }
    

      该接口暂时只定义了四个方法,即添加、更新,以及根据ID查询和分页查询。这是一个接口,并且和JPA类似,可以不用实现类。接下来我们编写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.pandy.blog.dao.ArticleMapper">
    
        <resultMap id="articleMap" type="com.pandy.blog.po.Article">
            <id column="id" property="id" jdbcType="INTEGER"/>
            <result column="title" property="title" jdbcType="VARCHAR"/>
            <result column="summary" property="summary" jdbcType="VARCHAR"/>
            <result column="user_id" property="userId" jdbcType="INTEGER"/>
            <result column="status" property="status" jdbcType="INTEGER"/>
            <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
            <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
            <result column="public_time" property="publicTime" jdbcType="TIMESTAMP"/>
        </resultMap>
    
        <sql id="base_column">
          title,summary,user_id,status,create_time,update_time,public_time
        </sql>
    
        <insert id="insertArticle" parameterType="Article">
            INSERT INTO
            tb_article(<include refid="base_column"/>)
            VALUE
            (#{title},#{summary},#{userId},#{status},#{createTime},#{updateTime},#{publicTime})
        </insert>
    
        <update id="updateArticle" parameterType="Article">
            UPDATE tb_article
            <set>
                <if test="title != null">
                    title = #{title},
                </if>
                <if test="summary != null">
                    summary = #{summary},
                </if>
                <if test="status!=null">
                    status = #{status},
                </if>
                <if test="publicTime !=null ">
                    public_time = #{publicTime},
                </if>
                <if test="updateTime !=null ">
                    update_time = #{updateTime},
                </if>
            </set>
            WHERE id = #{id}
        </update>
    
        <select id="queryById" parameterType="Long" resultMap="articleMap">
            SELECT id,<include refid="base_column"></include> FROM tb_article
            WHERE id = #{id}
        </select>
    
        <select id="queryArticlesByPage" resultMap="articleMap">
            SELECT id,<include refid="base_column"></include> FROM tb_article
            <where>
                <if test="article.title != null">
                    title like CONCAT('%',${article.title},'%')
                </if>
                <if test="article.userId != null">
                    user_id = #{article.userId}
                </if>
            </where>
            limit #{offset},#{pageSize}
    
        </select>
    </mapper>
    

      最后,我们需要手动指定mapper扫描的包:

    @SpringBootApplication
    @MapperScan("com.pandy.blog.dao")
    public class Application {
        public static void main(String[] args) throws Exception {
            SpringApplication.run(Application.class, args);
        }
    }
    

      好了,与MyBatis的集成也完成了,我们再测试一下:

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(classes = Application.class)
    public class ArticleMapperTest {
    
        @Autowired
        private ArticleMapper mapper;
    
        @Test
        public void testInsert() {
            Article article = new Article();
            article.setTitle("测试标题2");
            article.setSummary("测试摘要2");
            article.setUserId(1L);
            article.setStatus(1);
            article.setCreateTime(new Date());
            article.setUpdateTime(new Date());
            article.setPublicTime(new Date());
            mapper.insertArticle(article);
        }
    
        @Test
        public void testMybatisQuery() {
            Article article = mapper.queryById(1L);
            assertNotNull(article);
        }
    
        @Test
        public void testUpdate() {
            Article article = mapper.queryById(1L);
            article.setPublicTime(new Date());
            article.setUpdateTime(new Date());
            article.setStatus(2);
            mapper.updateArticle(article);
        }
    
        @Test
        public void testQueryByPage(){
            Article article = new Article();
            article.setUserId(1L);
            List<Article> list = mapper.queryArticlesByPage(article,10,0);
            assertTrue(list.size()>0);
        }
    }
    

      

    五、总结

        本文演示Spring boot与JdbcTemplate、JPA以及MyBatis的集成,整体上来说配置都比较简单,以前做过相关配置的同学应该感觉比较明显,Spring boot确实在这方面给我们提供了很大的帮助。后续的文章中我们只会使用MyBatis这一种方式来进行数据库的操作,这里还有一点需要说明一下的是,MyBatis的分页查询在这里是手写的,这个分页在正式开发中可以通过插件来完成,不过这个与Spring boot没什么关系,所以本文暂时通过这种手动的方式来进行分页的处理。

  • 相关阅读:
    PTA(Basic Level)1038.统计同成绩学生
    PTA(Basic Level)1026.程序运行时间
    PTA(Basic Level)1031.查验身份证
    PTA(Basic Level)1061.判断题
    Windows 常用命令
    Windows安装启动MySQL
    classpath路径配置
    IDEA远程调试
    Java Effective 读书笔记
    Try Catch Finally总结
  • 原文地址:https://www.cnblogs.com/paddix/p/8178943.html
Copyright © 2020-2023  润新知