IDEA+SpringMVC+Mybatis配置详细图文&报错&知识点(二)

  • Post author:
  • Post category:其他


执行环境:IDEA学生版,Tomcat7.0.94,jdk_1.8.0_144

二、Mybatis

参考链接:

https://blog.csdn.net/Youyou_0826/article/details/79642695

备注:Mybatis的部分配置过程与这个博主的一致,这里Dao层没用接口用的类,添加了一些报错和知识点(留待自己以后参考使用)。


例子中的数据库表为user,主键employee_id(int), 属性password(varchar)。

1.配置文件

(1)目录结构



建议所有的.xml、.properties文件都放在resources里,java里面只放class文件。

---小知识点---

1.mybatis:

利用少量的注解、xml可以简化query语句的书写,是一个管理数据库接口的框架。

通过JDBC查询得到ResultSet对象,
遍历ResultSet对象,并将每行数据暂存到HashMap实例中,以结果集的字段名或字段别名为键,以字段值为值,
根据ResultMap标签的type属性通过反射实例化领域模型,
根据ResultMap标签的type属性和id、result等标签信息将HashMap中的键值对填充到领域模型实例中返回。

ResultMap即在mapper/mybatis-xxx.xml文件里配置领域模型及实体映射关系。

(2)log4j.properties

配置日志文件,error文件和debug文件。

# 配置
log4j.rootLogger=debug,stdout,D,E

### 输出信息到控制台 ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

### 输出DEBUG 级别以上的日志到文件F://logs/debug.log ###
log4j.appender.D=org.apache.log4j.FileAppender
log4j.appender.D.File=F:/IDEA_Projects/Demo/logs/debug.log
log4j.appender.D.Append=true
log4j.appender.D.Threshold=DEBUG
log4j.appender.D.layout=org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### 输出ERROR 级别以上的日志到文件F://logs/error.log ###
log4j.appender.E=org.apache.log4j.FileAppender
log4j.appender.E.File=F:/IDEA_Projects/Demo/logs/error.log
log4j.appender.E.Append=true
log4j.appender.E.Threshold=ERROR
log4j.appender.E.layout=org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%

(3)jdbc.properties

单独的文件配置数据库的driverClass、url、username、password信息,方便修改维护。

database.driver=com.mysql.cj.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
database.username=用户名(不用引号)
database.password=密码(注意每一行后不要有空格,不然会解析错误)


1.报错:mybatis Could not get JDBC Connection


解决:mysql-connector版本过低,pom.xml改了mysql-connector为8.0.11版本后,jdbc.properties里面的database.driver包名也应该从com.mysql.jdbc.Driver改为com.mysql.cj.jdbc.Driver。


2.报错:The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized or represents more than one time zone


解决:时区问题,设置时区serverTimezone为UTC比北京时间早8个小时,同时把utf-8中文乱码问题一起配置了(url问号后面部分)。

(4)mybatis-config.xml

将jdbc.properties数据库基本信息加载进去,配置sql的mapper文件位置、sqlSession的bean信息和dao层文件自动扫描映射。

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <context:component-scan base-package="com.example"/>

    <!--带有classpath:的目录对应打包后的target/classes文件夹-->

    <context:property-placeholder location="classpath:mybatis/jdbc.properties"/>

    <!--从jdbc.properties导入数据库信息,bean的id为jdbcDataSource-->
    <bean id="jdbcDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${database.driver}"/>
        <property name="url" value="${database.url}"/>
        <property name="username" value="${database.username}"/>
        <property name="password" value="${database.password}"/>
    </bean>

    <!--配置sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="jdbcDataSource"/><!--ref对应上边的bean id="jdbcDataSource"的命名空间的参数,及对应类的实例-->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/><!--mapper数据库query映射xml文件的位置-->
    </bean>

    <!--注册sqlSession的bean,并联系上面的sqlSessionFactory,就不用通过new SqlSessionFactoryBuilder()进行sqlSession的建立了-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

    <!--数据访问层DAO层(接口)扫描位置-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.example.dao"/><!--Dao层-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

    <!--add transaction operation-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="jdbcDataSource"/>
    </bean>
</beans>
---小知识点---

2.SqlSessionFactoryBuilder 与 SqlSessionFactoryBean的区别:

mybatis中使用SqlSessionFactoryBuilder创建session工厂;
mybatis-spring整合时使用SqlSessionFactoryBean替代SqlSessionFactoryBuilder来创建session工厂.
mybatis-config.xml里面使用了SqlSessionFactoryBean所以是后者。

就不用通过new SqlSessionFactoryBuilder()进行sqlSession的建立了,直接以下格式使用:
@Resource(name="sqlSession")
private SqlSession sqlSession;
User user = sqlSession.selectOne("selectUserByUserId",user_id);//这里selectone对应mybatis-user.xml里的<selsect>标签,selectUserByUserId是其id

(5)mapper/mybatis-user.xml

等于说是具体的查询等query语句的实现,都通过在mybatis-user.xml的映射里构成sql语句,再经由mybatis-config得到数据库信息和连接数据库的配置,

访问数据库后再传回mybatis-user.xml构成User实体对象。

UserDao -> mybatis-config.xml -> mybatis-user.xml -> SQL_query -> mybatis-user.xml -> User -> UserDao

最好是一个实体建立一个mybatis-entity.xml,里面通过select或者其他选项里面的resultMap匹配id来匹配实体类确定输出。

<?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.example.dao.UserDao">

    <!--领域模型1,这样是根据setter构造的User实例,type为所用实体类型:entity.User,这样可将resultMap的结果直接生成User对象
        autoMapping默认为true,即自动映射,查找与sql字段名小写同名的属性名,自动调用setter方法,不自动映射的话需要注明映射关系-->
    <resultMap id="getUser" type="com.example.entity.User" autoMapping="false">
        <!--column对应SQL查询的字段属性,property对应实体类属性,User留一个无参数构造函数即可-->
        <id property="user_id" column="employee_id" jdbcType="INTEGER"/>
        <result property="password" column="password" jdbcType="VARCHAR"/>
    </resultMap>

    <!--领域模型2,这样是根据传参的构造函数创建的User实例,constructor里arg元素必须与User构造器的参数顺序一致,如User(int user_id,String password)-->
    <resultMap id="getUser2" type="com.example.entity.User">
        <constructor>
            <idArg column="employee_id" javaType="_int"/><!--column对应的是sql查询属性-->
            <arg column="password" javaType="String"/>
        </constructor>
    </resultMap>

    <select id="selectUserByUserId" resultMap="getUser" parameterType="java.lang.Integer">
        SELECT
            employee_id,
            password
        FROM user
        WHERE employee_id = #{id, jdbcType=INTEGER}
    </select><!--如果UserDao里有@param("id")的注解,mapper里就必须一致为id,否则也可以写成#{0}表示第1个参数 -->

</mapper>

更多ResultMap领域模型格式参考:

https://www.cnblogs.com/fsjohnhuang/p/4076592.html

---小知识点---

3.JdbcType:

ARRAY(2003),BIT(-7),TINYINT(-6),SMALLINT(5),INTEGER(4),BIGINT(-5),FLOAT(6),REAL(7),
DOUBLE(8),NUMERIC(2),DECIMAL(3),CHAR(1),VARCHAR(12),LONGVARCHAR(-1),DATE(91),TIME(92),
TIMESTAMP(93),BINARY(-2),VARBINARY(-3),LONGVARBINARY(-4),NULL(0),OTHER(1111),BLOB(2004),CLOB(2005),
BOOLEAN(16),CURSOR(-10),UNDEFINED(-2147482648),NVARCHAR(-9),NCHAR(-15),NCLOB(2011),STRUCT(2002),JAVA_OBJECT(2000),
DISTINCT(2001),REF(2006),DATALINK(70),ROWID(-8),LONGNVARCHAR(-16),SQLXML(2009),DATETIMEOFFSET(-155);

jdbctype要对应,写int等不会被识别到。

2.示例

(1)User.java

package com.example.entity;

public class User {

    private int user_id;
    private String password;

    public User(int user_id,String password){
        this.user_id=user_id;
        this.password=password;
    }

    public User(){
    }//mapper里用的setter领域模型1,这里要有一个默认空构造函数

    public int getUser_id() {
        return user_id;
    }

    public void setUser_id(int user_id) {
        this.user_id = user_id;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

(2)UserDao.java

package com.example.dao;

import com.example.entity.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

//这个注解是告诉Spring,让Spring创建一个名字为userDao实例,Service里面可以用@Resource将该实例注入
@Repository(value="userDao") //public interface Repository extends annotation.Annotation,Repository数据仓库
public class UserDao {

    @Resource(name="sqlSession")
    private SqlSession sqlSession;//bean id为sqlSession,在mybatis-config.xml里面配置的,属于类org.mybatis.spring.SqlSessionTemplate

    public User selectUserByUserId(@Param("id") int user_id){//@param注解与mapper里的select的#{id}一致
        User user = sqlSession.selectOne("selectUserByUserId",user_id);
        System.out.println("====================user is:"+user.getUser_id()+"=================");
        return user;
    }

}

(3)UserService.java

package com.example.service;

import com.example.dao.UserDao;
import com.example.entity.User;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service(value="userService")//public interface Service extends annotation.Annotation 注明是个Service,注册bean
public class UserService {

    @Resource(name="userDao") //public interface Resource extends annotation.Annotation
    private UserDao userDao;//通过@Resource获取之前在Dao层@Repository注册的UserDao实例

    public UserService(){

    }

    public User login(User _user) {
        User user=userDao.selectUserByUserId(_user.getUser_id());
        //业务层判断,如果密码错误,则返回null
        if (!user.getPassword().equals(_user.getPassword()))
            return null;
        else
            return user;
    }
}

(4)UserController.java

package com.example.controller;

import com.example.entity.User;
import com.example.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;


@Controller //controller注释代表这个类是个控制器
@RequestMapping("/user")//这里的RequestMapping注释指的是前端html的根路径
public class UserController {

    @Resource(name="userService")
    private UserService userService;//得到前面@Service注册的UserService实例

    //拦截到了浏览器或其他的访问url为http://127.0.0.1:8080/Demo/user/login则调用该方法
    @RequestMapping("/login")//结合上面的/user,则html的form表单的action路径即为/user/login
    public String login() {
        User user=this.userService.login(new User(201,"201"));
        if(user!=null)
            return "index";//返回/views/index
        else
            return "404";//返回/views/404
    }
}
---小知识点---

4.注解:

@param注解使得UserDao里的接口传的参数与mapper里的select等语句里的#{xx}要一致,否则无效

@Component一般组件 @Service业务层 @Controller控制层,@Repository数据访问层
这几个算是注册bean,配套@Resource或@Autowired使用。

格式:
@Repository(value="userDao") 
public class UserDao {
} //Dao层创建实例,标识为Spring Bean,在applicationContext.xml中配置<context:component-scan/>自动扫描即可。

@Resource(name="userDao")
private UserDao userDao;//注入实例,当@Resource被用到某个域/方法/类,container会发送一个它的实例在初始化的时候

另外:
@Resource也可以注入xml文件里注明的bean类,如xml里注明了
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"><!--class类名-->
    <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
则可以如下调用
@Resource(name="sqlSession")
private SqlSession sqlSession;

@Resource与@Autowired区别:
@Resource(name="userDao")会先查找名字为userDao的bean,如果没有找到该类,则以UserDao类型进行匹配
@Resource默认按byName自动注入,@Autowired默认按byType进行匹配,如果找到多个bean,则按照byName方式进行匹配。

@Repository:
@Repository只用在dao层,因为不仅识别为了bean,还会将标注的类中抛出的数据访问异常封装为Spring的数据访问异常类。

机制:
spring容器初始化时自动扫描<beans>的base-package指定包里的所有class文件,所有@Component、 @Service、@Controller、@Repository的类都将注册为Spring Bean。
Spring会自动创建相应的BeanDefinition对象,并注册到ApplicationContext中,这些类就成了Spring受管组件。

3.运行

tomcat运行,浏览器里url栏输入http://127.0.0.1:8080/Demo/user/login

---小知识点---

5.Dao&Service是否要用接口和Impl实现类:

为什么要用Service接口?是让表示层不依赖于业务层的具体实现。为什么要用DAO接口?是让业务层不依赖于持久层的具体实现。举个例子,用DAO接口,那么持久层用Hibernate,还是用iBatis,还是 JDBC,随时可以替换,不用修改业务层Service类的代码。
不用接口的话,假如修改了dao中的代码,因为service引用了dao中的类,那么也要改变service里面的代码,改完之后要重新编译运行,当项目比较大的时候,编译和运行很浪费时间的,而且会产生一些意外,本来只要编译dao中的代码,现在不光要编译dao中的代码,还要编译service。因为你不用接口,间接着action里的代码也要改,因为action中引用了service中的类,到最后,就变成了,牵一发而动全身。
为什么要写Imp实现类呢,是因为后期维护的时候如果要修改功能只需要修改实现类里面的那个代码,而不需要修改其他包的代码。

本文例中没用接口、实现分离。



版权声明:本文为qq_43533416原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。