Spring Boot (2)--连接数据库(mysql)

本章内容

  • 日志输出
  • 连接mysql及使用JPA
  • 使用alibaba.druid
  • 开启事务

 

日志输出

        在Spring Boot中默认使用的是slf4j+logback,并且spring boot规定配置文件的命名格式最好是xxx-spring.[xml,yml,properties],这样可以让boot来管理你的配置文件。但如果你坚持自定义命名,boot也可以帮你实现,通过在application.yml里配置logging.config=classpath:mylog-config.xml即可

基本配置

主要包含如下几个元素:

  • logging.file,日志文件名称,如果没有配置,默认就是项目的目录下生成一个spring.log的日志文件,默认情况下是10MB切分一个文件。
  • logging.path,日志目录,可以是绝对路径,也可骒相对路径,例如:/var/project/logs。
  • logging.level,配置日志级别,配置粒度可以到包级别,例如:logging.level.com.xps=INFO意思就是com.xps包下的所有类的日志输出级别都是INFO。logging.level是日志级别的前缀。LEVEL选项包括:TRACE

如上,可以简单的输出日志信息,但如果我们想定义更为复杂的日志,例如我们要自定义日志格式并且不同的环境需要不同的输出内容等等,那么,我们就需要有一个logback-spring.xml的日志配置文件了。

logback日志文件格式

根节点是,有以下几个属性:

  • scan:默认为true,当为true时,配置文件如果发生改变,将会被重新加载。
  • scanPeriod:监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认为毫秒。当scan为true时生效。默认为1分钟。
  • debug:默认为false,当为true时将打印出logback内部日志信息,实时查看logback运行状态。

此外,它还有五个子节点

节点一

必选节点,用来指定最基础的日志输出级别,有level属性,默认DEBUG。其它级别还有:TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF。其可以包含多个appender元素。例如:


        
        
    

节点二

logger上下文,默认的名称是"default",可以使用此节点设置其它名称,用于区分不同应用程序的记录。一旦设置不能修改,使用时,可以通过%contextName来打印日志上下文名称。(此属性不是必须的,可有可无,所以我们一般都不设置它)。

my-project

节点三

主要用来定义属性键值对的,有name和value两个属性,通过定义的值可以插入到logger上下文中。定义的变量可以使用${}来获取变量。如果value的值通过application.yml传过来,使用springProperty。

或者使用:

节点四

用来格式化日志输出,有俩个属性name和class,class用来指定哪种输出策略,常用的是控制台输出和文件输出。

控制台输出



 
        
            %d{yyyy-MM-dd HH:mm:ss} [%level] [%class:%line] - %m %n
        
    
    
        
            %d{yyyy-MM-dd HH:mm:ss} %contextName [%level] [%class:%line] - %m %n
        
    
  
        
        
    

可以看到有encoder和layout两种标签,它们都可以将事件轮换成格式化的日志,但是一般控制台输出使用layout,文件输出使用encoder。

输出到文件

随着项目的运行时间增长,日志文件也越来越大,所以需要把日志文件切分成多个文件以便于读取分析或复制保存。RollingFileAppender就是用来切分日志文件的:


        
        
            
            ERROR
            
            DENY
            
            ACCEPT
        
    
        ${logPath}/info.${logAppName}.log
        
        
            
            ${logPath}/info.${logAppName}.%d{yyyy-MM-dd}.log
            
            90
            
            
        
        
        
            UTF-8
            %d [%thread] %-5level %logger{36} %line - %msg%n
        
    


    
    
        Error
    
    
    ${logPath}/error.${logAppName}.log
    
    
        
        ${logPath}/error.${logAppName}.%d{yyyy-MM-dd}.log
        
        90
        
        
    
    
    
        UTF-8
        %d [%thread] %-5level %logger{36} %line - %msg%n
    

节点五

root节点的子类,指定包下的类输出日志的级别,它包括了两个属性:

  • level:日志级别,如果未指定则继承root的level
  • addtivity:是否将logger的打印信息向上级传递,默认为true

除此之外,它还可以包含子节点。可以有多个logger节点,如果有包重叠的话以范围小的生效。

例如:


    
    

多环境日志

在做项目时,我们都会有开发环境,测试环境,生产环境这样的划分,只需要指定环境变量即可切换不同的环境。然后在,application.yml中配置spring.profiles.active=dev来切换


    
        
        
        
    

连接数据库及JPA

我们以mysql+jpa为例,springboot连接数据库还是比较简单的,直接在application.yml中配置即可(或各环境的yml文件中)。

先引入相关的jar包:



   org.springframework.boot
   spring-boot-starter-data-jpa



   mysql
   mysql-connector-java



   com.querydsl
   querydsl-jpa


   com.querydsl
   querydsl-apt
   provided



   com.alibaba
   druid
   1.1.3

数据库连接的配置:

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    platform: mysql
    url: jdbc:mysql://localhost:3306/jsc?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root
  jpa:
   properties:
    hibernate:
      hbm2ddl:
        auto: update
      dialect: org.hibernate.dialect.MySQL5InnoDBDialect
  show-sql: true

这样就配置好了,但启动后可能你会发现一个小错误(但并不影响项目运行):

Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is 
`com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and 
manual loading of the driver class is generally unnecessary.

是 说com.mysql.jdbc.Driver已经不使用了,而改为com.mysql.cj.jdbc.Driver,那我们就改成后者即可。

新增dao包,新建UserDao类并继承JpaRepository。

public interface UserDao extends JpaRepository {

    User queryByUserName(String userName);

    User findByUserNameOrEmail(String userName,String email);

}

JPA是一个ORM规范的框架,基本的CRUD操作在JpaRepository类中已经定义,直接拿来使用即可。上类中的两个方法是jpa的另一个比较方便的特点,就是根据方法名称来生成sql,例如,上面的findByUserNameOrEmail就是使用userName或email查询,其生成的sql,类似于:

SELECT 
  user0_.user_id AS user_id1_0_,
  user0_.create_time AS create_t2_0_,
  user0_.email AS email3_0_,
  user0_.nick_name AS nick_nam4_0_,
  user0_.pass_word AS pass_wor5_0_,
  user0_.reg_time AS reg_time6_0_,
  user0_.user_name AS user_nam7_0_ 
FROM
  t_test_user user0_ 
WHERE user0_.user_name = 'aaa' or user0_.email=''

总体上来说,目前操作SQL主要有两种形式,一种是面向对象不写sql,根据配置生成相关的sql操作,像jpa,Hibernate等等,另一种是可以配置sql的用的比较多的就是mybatis了。关于jpa的其它特性请自行参考:https://docs.spring.io/spring-data/jpa/docs/2.1.3.RELEASE/reference/html/

 

使用druid

druid是一个可以监控数据库连接及sql的项目,我们可以引入此项目来监控数据库连接等。

相关的配置:

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    driver-class-name: com.mysql.cj.jdbc.Driver
    platform: mysql
    url: jdbc:mysql://localhost:3306/jsc?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 'x'
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    filters: stat,wall
    logSlowSql: true
#自定义druid需要的相关参数
druid:
  console:
    urlMapping: '/druid/*'
    login-username: admin
    login-password: admin
  url-pattern: '/*'
  exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'

然后,写一个配置类来加载这些内容

@Configuration
public class DruidConfiguration {

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

    private static final String DB_PREFIX = "spring.datasource";

    @Value("${druid.console.urlMapping}")
    private String urlMapping;
    @Value("${druid.console.login-username}")
    private String loginUserName;
    @Value("${druid.console.login-password}")
    private String loginPassword;

    @Value("${druid.url-pattern}")
    private String urlPattern;
    @Value("${druid.exclusions}")
    private String exclusions;

    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean servletRegistrationBean =
                new ServletRegistrationBean(new StatViewServlet(), urlMapping);
        // IP白名单
        servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
        // IP黑名单(共同存在时,deny优先于allow)
        servletRegistrationBean.addInitParameter("deny", "192.168.1.22");
        //控制台管理用户
        servletRegistrationBean.addInitParameter("loginUsername",loginUserName);
        servletRegistrationBean.addInitParameter("loginPassword",loginPassword);
        //是否能够重置数据 禁用HTML页面上的“Reset All”功能
        servletRegistrationBean.addInitParameter("resetEnable", "false");
        return servletRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns(urlPattern);
        filterRegistrationBean.addInitParameter("exclusions", exclusions);

        return filterRegistrationBean;
    }

    //解决 spring.datasource.filters=stat,wall无法正常注册进去
    @Component
    @ConfigurationProperties(prefix = DB_PREFIX)
    @Getter
    @Setter //lombok包只的标签,可以省略写get,set方法
    class IDataSourceProperties {
        private String url;
        private String username;
        private String password;
        private String driverClassName;
        private int initialSize;
        private int minIdle;
        private int maxActive;
        private int maxWait;
        private int timeBetweenEvictionRunsMillis;
        private int minEvictableIdleTimeMillis;
        private String validationQuery;
        private boolean testWhileIdle;
        private boolean testOnBorrow;
        private boolean testOnReturn;
        private boolean poolPreparedStatements;
        private int maxPoolPreparedStatementPerConnectionSize;
        private String filters;
        private String connectionProperties;

        @Bean 
    @Primary  //在同样的DataSource中,首先使用被标注的DataSource
        public DataSource dataSource() {
            DruidDataSource datasource = new DruidDataSource();
            datasource.setUrl(url);
            datasource.setUsername(username);
            datasource.setPassword(password);
            datasource.setDriverClassName(driverClassName);
            datasource.setInitialSize(initialSize);
            datasource.setMinIdle(minIdle);
            datasource.setMaxActive(maxActive);
            datasource.setMaxWait(maxWait);
            datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
            datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
            datasource.setValidationQuery(validationQuery);
            datasource.setTestWhileIdle(testWhileIdle);
            datasource.setTestOnBorrow(testOnBorrow);
            datasource.setTestOnReturn(testOnReturn);
            datasource.setPoolPreparedStatements(poolPreparedStatements);
            datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
            try {
                datasource.setFilters(filters);
            } catch (SQLException e) {
                logger.error("druid configuration initialization filter: " + e);
            }
            datasource.setConnectionProperties(connectionProperties);
            return datasource;
        }
    }

    public String getLoginUserName() {
        return loginUserName;
    }

    public void setLoginUserName(String loginUserName) {
        this.loginUserName = loginUserName;
    }

    public String getLoginPassword() {
        return loginPassword;
    }

    public void setLoginPassword(String loginPassword) {
        this.loginPassword = loginPassword;
    }

    public String getUrlMapping() {
        return urlMapping;
    }

    public void setUrlMapping(String urlMapping) {
        this.urlMapping = urlMapping;
    }

    public String getUrlPattern() {
        return urlPattern;
    }

    public void setUrlPattern(String urlPattern) {
        this.urlPattern = urlPattern;
    }

    public String getExclusions() {
        return exclusions;
    }

    public void setExclusions(String exclusions) {
        this.exclusions = exclusions;
    }
}

OK,到此已经配置完成了,是不是很简单呢。然后我们就可以在浏览器中访问:

http://localhost:8000/druid/login.html就会出现

Spring Boot (2)--连接数据库(mysql)_第1张图片

 

输入我们配置的loginuserName和password就可以了,进来之后就可以看到

Spring Boot (2)--连接数据库(mysql)_第2张图片

 

事务配置

        在做项目中,事务也分为分布式事务和单库事务,分布式事务这个比较复杂再这里不再阐述,单库事务就是在同一个数据库中的事务,spring boot已经为我们注入的声明式事务机制。只需要我们在业务方法上使用@Transactional即可支持事务。例如:

@Override
@Transactional
public User modify(User user) {
    if(user==null)
        user = new User();
    user.setUserName("ddd");
    user.setPassWord("ddd");
    user.setEmail("[email protected]");
    user.setNickName("王老d");
    user.setRegTime("2018-12-19 11:19:10");
    user.setCreateTime(new Date());
    userDao.save(user);
//会抛出异常,观察数据库中有没有把ddd保存成功,如果没有保存成功,那就说明有事务加持。
    int a = Integer.parseInt("a");

    user = new User();
    user.setUserName("ccc");
    user.setPassWord("ccc");
    user.setEmail("[email protected]");
    user.setNickName("王老七");
    user.setRegTime("2018-12-19 11:19:10");
    user.setCreateTime(new Date());
    userDao.save(user);
    return user;
}

另外,我们也可以通过查看日志来判断有无事务参与。

[DEBUG] [org.springframework.core.log.LogFormatUtils:90] - GET "/modifyUser/3", parameters={}

2018-12-21 15:10:30 [DEBUG] [org.springframework.web.servlet.handler.AbstractHandlerMapping:420] - Mapped to public com.xps.sc.springbootdemo.entity.User com.xps.sc.springbootdemo.controller.HelloBootController.modifyUser(java.lang.Long)

2018-12-21 15:10:30 [DEBUG] [org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor:86] - Opening JPA EntityManager in OpenEntityManagerInViewInterceptor

2018-12-21 15:10:30 [DEBUG] [org.springframework.orm.jpa.JpaTransactionManager:355] - Found thread-bound EntityManager [SessionImpl(1299376334)] for JPA transaction

2018-12-21 15:10:30 [DEBUG] [org.springframework.transaction.support.AbstractPlatformTransactionManager:372] - Creating new transaction with name [com.xps.sc.springbootdemo.service.impl.UserServiceImpl.modify2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

2018-12-21 15:10:30 [DEBUG] [org.hibernate.engine.transaction.internal.TransactionImpl:56] - On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false

2018-12-21 15:10:30 [DEBUG] [org.hibernate.engine.transaction.internal.TransactionImpl:78] - begin

2018-12-21 15:10:30 [DEBUG] [org.springframework.orm.jpa.JpaTransactionManager:419] - Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@60fad735]

2018-12-21 15:10:30 [DEBUG] [org.springframework.data.repository.core.support.TransactionalRepositoryProxyPostProcessor$AbstractFallbackTransactionAttributeSource:355] - Adding transactional method 'save' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

2018-12-21 15:10:30 [DEBUG] [org.springframework.orm.jpa.JpaTransactionManager:355] - Found thread-bound EntityManager [SessionImpl(1299376334)] for JPA transaction

2018-12-21 15:10:30 [DEBUG] [org.springframework.transaction.support.AbstractPlatformTransactionManager:473] - Participating in existing transaction

2018-12-21 15:10:30 [DEBUG] [org.springframework.beans.CachedIntrospectionResults:186] - Not strongly caching class [com.xps.sc.springbootdemo.entity.User] because it is not cache-safe

2018-12-21 15:10:30 [DEBUG] [org.hibernate.engine.jdbc.spi.SqlStatementLogger:94] - select next_val as id_val from hibernate_sequence for update

Hibernate: select next_val as id_val from hibernate_sequence for update

2018-12-21 15:10:30 [DEBUG] [org.hibernate.engine.jdbc.spi.SqlStatementLogger:94] - update hibernate_sequence set next_val= ? where next_val=?

Hibernate: update hibernate_sequence set next_val= ? where next_val=?

2018-12-21 15:10:30 [DEBUG] [org.hibernate.event.internal.AbstractSaveEventListener:136] - Generated identifier: 25, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator

2018-12-21 15:10:31 [DEBUG] [org.springframework.transaction.support.AbstractPlatformTransactionManager:836] - Initiating transaction rollback

2018-12-21 15:10:31 [DEBUG] [org.springframework.orm.jpa.JpaTransactionManager:553] - Rolling back JPA transaction on EntityManager [SessionImpl(1299376334)]

2018-12-21 15:10:31 [DEBUG] [org.hibernate.engine.transaction.internal.TransactionImpl:136] - rolling back

2018-12-21 15:10:31 [DEBUG] [org.springframework.orm.jpa.JpaTransactionManager:623] - Not closing pre-bound JPA EntityManager after transaction

2018-12-21 15:10:31 [ERROR] [com.xps.sc.springbootdemo.controller.HelloBootController:64] - HelloBootController modify is error

另一种事务配置方式是全局性事务,不需要我们标注@Transactional也可以实现事务,和以前使用spring的aop实现的事务控制一样,在spring boot中也可以实现。

首先,我们需要引入aop包的支持:


   org.springframework.boot
   spring-boot-starter-aop

然后,与druid配置类似,也需要有一个配置类来设置aop一些属性,不用解释,相信大家也能看懂。与以前使用spring的xml配置元素一样

@Aspect
@Configuration
public class TransactionAdviceConfig {
    private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.xps.sc.springbootdemo.service.*.*(..))";

    @Autowired
    private PlatformTransactionManager transactionManager;//会自动注入,
//提示错误不用管,如果使用的是jpa则会自动注入成
//JpaPlatformTransactionManager

    @Bean
    public TransactionInterceptor txAdvice() {

        DefaultTransactionAttribute txAttr_REQUIRED = new DefaultTransactionAttribute();
        txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        DefaultTransactionAttribute txAttr_REQUIRED_READONLY = new DefaultTransactionAttribute();
        txAttr_REQUIRED_READONLY.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        txAttr_REQUIRED_READONLY.setReadOnly(true);

        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        source.addTransactionalMethod("add*", txAttr_REQUIRED);
        source.addTransactionalMethod("save*", txAttr_REQUIRED);
        source.addTransactionalMethod("delete*", txAttr_REQUIRED);
        source.addTransactionalMethod("update*", txAttr_REQUIRED);
        source.addTransactionalMethod("modify*", txAttr_REQUIRED);
        source.addTransactionalMethod("exec*", txAttr_REQUIRED);
        source.addTransactionalMethod("set*", txAttr_REQUIRED);
        source.addTransactionalMethod("get*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("query*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("find*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("list*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("count*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("is*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("*", txAttr_REQUIRED);
        return new TransactionInterceptor(transactionManager, source);
    }

    @Bean
    public Advisor txAdviceAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        return new DefaultPointcutAdvisor(pointcut, txAdvice());
    }
}

然后同样使用上述方法不加@Transactional测试,效果一样。

总结:

      本章主要是介绍了spring boot与mysql数据库,jpa的结合使用。也说了一些事务配置。这也是我们做一般项目必须使用的。所以以此笔记梳理记忆。接下来,spring boot还可以结合mybatis框架,redis缓存及MQ消息中间件等等。努力学习ing。

参考:https://blog.csdn.net/inke88/article/details/75007649,

http://www.ityouknow.com/springboot/2016/08/20/spring-boo-jpa.html

你可能感兴趣的:(Spring,Boot)