最近在为新项目搭建SpringMVC+Mybatis+Spring的环境。在这个过程中发现了许多问题,所以把这些总结下。
首先,提出一个问题。当数据库表中有些字段不存在时,且这些字段没有默认值,但是我们希望在程序中取出来的时候这些字段对应的属性能得到一个默认值。这个需要怎么做到呢?
当我们熟悉Mybatis的时候,我们就可以很快的知道怎么解决了,那就是使用TypeHandler。
+ 例如,现在有一个表,Player
表结构如下
create table player(
name varchar2(60),
id number(10),
age number(3),
primary key(id)
)
此时,假设name和age都有可能在插入时不赋值,现在要求当name和age在数据库中没有值的时候name在程序中的值对应为“”,age为-1
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD SQL MAP Config 3.2//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--
Mybatis在使用resultMap来映射查询结果中的列,如果查询结果中包含空值的列(不是null),则Mybatis在映射的时候,不会映射这个字段
-->
<settings>
<setting name="callSettersOnNulls" value="true"/>
</settings>
onfiguration>
首先我们需要配置mybatis-config.xml(mybatis配置文件)
<settings>
<setting name="callSettersOnNulls" value="true"/>
</settings>
然后将其加入mybatis-spring.xml(spring和mybatis集成配置文件)
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 自动扫描 https://zhidao.baidu.com/question/73410686.html-->
<context:component-scan base-package="com.wangcc.ssm" />
<!-- 引入配置文件 -->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbcoracleHome.properties" />
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="${initialSize}"></property>
<!-- 连接池最大数量 -->
<property name="maxActive" value="${maxActive}"></property>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${maxIdle}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${minIdle}"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${maxWait}"></property>
</bean>
<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件
在定义SqlSessionFactoryBean的时候,dataSource属性是必须指定的,它表示用于连接数据库的数据源。当然,我们也可以指定一些其他的属性,下面简单列举几个:
mapperLocations:它表示我们的Mapper文件存放的位置,当我们的Mapper文件跟对应的Mapper接口处于同一位置的时候可以不用指定该属性的值。
configLocation:用于指定Mybatis的配置文件位置。如果指定了该属性,那么会以该配置文件的内容作为配置信息构建对应的SqlSessionFactoryBuilder,但是后续属性指定的内容会覆盖该配置文件里面指定的对应内容。
typeAliasesPackage:它一般对应我们的实体类所在的包,这个时候会自动取对应包中不包括包名的简单类名作为包括包名的别名。多个package之间可以用逗号或者分号等来进行分隔。
typeAliases:数组类型,用来指定别名的。指定了这个属性后,Mybatis会把这个类型的短名称作为这个类型的别名,前提是该类上没有标注@Alias注解,否则将使用该注解对应的值作为此种类型的别名。
-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 自动扫描mapping.xml文件 -->
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
<property name="typeAliasesPackage" value="com.wangcc.ssm.entity"></property>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.wangcc.ssm.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
先看String类型的name
要求字段类型jdbcType=VARCHAR,javaType=String在Java程序中取出时,都变成“”,我们需要怎么做呢。我们看下代码。
package com.wangcc.ssm.typehandler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
public class StringTypeHandler implements TypeHandler<String> {
@Override
public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
// TODO Auto-generated method stub
return (rs.getString(columnName) == null)
? "": rs.getString(columnName);
}
@Override
public String getResult(ResultSet rs, int columnIndex) throws SQLException {
// TODO Auto-generated method stub
return (rs.getString(columnIndex) == null)
? "" : rs.getString(columnIndex);
}
@Override
public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
// TODO Auto-generated method stub
return (cs.getString(columnIndex) == null)
? "": cs.getString(columnIndex);
}
}
代码比较简单,即是当值为null时,就返回“”,其他情况便返回数据库中的字段值。
这个时候我们需要去Mapper配置文件中配置。
<?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.wangcc.ssm.dao.PlayerDao">
<resultMap type="com.wangcc.ssm.entity.Player" id="Player">
<result property="name" column="name" typeHandler="com.wangcc.ssm.typehandler.StringTypeHandler"/>
<result property="id" column="id"/>
<result property="age" column="age" typeHandler="com.wangcc.ssm.typehandler.IntegerTypeHandler"/>
<result property="teamId" column="team_id"/>
</resultMap>
<!-- jdbcType
使用MyBatis框架做更新操作时,在该字段需要更新的内容为空时,就会出现1111错误,也就是无效的列类型,这个时候你就要使用jdbcType。
由于参数出现了null值,对于Mybatis,如果进行操作的时候,没有指定jdbcType类型的参数,mybatis默认jdbcType.OTHER导致,给参数加上jdbcType可解决(注意大小写)
-->
<insert id="insertPlayer" parameterType="Player">
insert into player(id,name,age,team_id) values(s_test_player.nextval,#{name,jdbcType=VARCHAR},#{age},#{teamId})
</insert>
<select id="selectById" parameterType="Integer" resultMap="Player">
select * from player where id=#{id}
</select>
<select id="selectByTeamId" parameterType="Integer" resultMap="Player">
select * from player where team_id=#{id}
</select>
</mapper>
<result property="name" column="name" typeHandler="com.wangcc.ssm.typehandler.StringTypeHandler"/>
需要在resultMap中的result里添加属性typeHandler。
这样我们取出数据库中为空的name时,在程序中的得到的是“”。
我们再来看看age(基本类型)的typeHandler该怎么做
package com.wangcc.ssm.typehandler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
public class IntegerTypeHandler implements TypeHandler<Integer>{
@Override
public void setParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public Integer getResult(ResultSet rs, String columnName) throws SQLException {
// TODO Auto-generated method stub
if(rs.getObject(columnName)!=null) {
return rs.getInt(columnName);
}
return -1;
}
@Override
public Integer getResult(ResultSet rs, int columnIndex) throws SQLException {
// TODO Auto-generated method stub
if(rs.getObject(columnIndex)!=null) {
return rs.getInt(columnIndex);
}
return -1;
}
@Override
public Integer getResult(CallableStatement cs, int columnIndex) throws SQLException {
// TODO Auto-generated method stub
if(cs.getObject(columnIndex)!=null) {
return cs.getInt(columnIndex);
}
return -1;
}
}
首先,因为泛型不能用在基本类型中,所以需要使用int的包装类Integer来做TypeHandler的泛型。这里的代码,我们可以看到和上面String类型的TypeHandler有些区别,用了getObject()来判断是否为null,然后再来赋值。为什么需要这样呢,为什么不直接用getInt来判断呢?这是因为get基本类型时JDBC自动回把数据库中没没有值的基本类型对应字段返回设为0(int) double(0.0)等, 所以这个时候呢,当为int类型时,我们是无法知道到底是本身就是0还是数据库中该字段没有值。所以需要用getObject先来判断一下。
最后,我们说一下jdbcType
在前面我们说过假设name和age都有可能在插入时不赋值,而当使用MyBatis框架做插入更新操作时,在该字段需要更新的内容为空时,就会出现1111错误,也就是无效的列类型。这个时候就需要使用jdbcType.
由于参数出现了null值,对于Mybatis,如果进行操作的时候,没有指定jdbcType类型的参数,mybatis默认jdbcType.OTHER导致,给参数加上jdbcType可解决(注意大小写)。这也就是上面的Mapper配置文件中为什么需要出现jdbcType.
给出相应的测试类和相关代码
package com.wangcc.test;
import javax.annotation.Resource;
import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.alibaba.fastjson.JSON;
import com.wangcc.ssm.entity.Coach;
import com.wangcc.ssm.entity.Player;
import com.wangcc.ssm.entity.Team;
import com.wangcc.ssm.service.CoachService;
import com.wangcc.ssm.service.PlayerService;
import com.wangcc.ssm.service.TeamService;
//不支持junit4.4,需要更高版本的junit
//http://blog.csdn.net/zacry/article/details/37052973 http://blog.csdn.net/bruce128/article/details/9792283
@RunWith(SpringJUnit4ClassRunner.class) //表示继承了SpringJUnit4ClassRunner类
@ContextConfiguration(locations = {"classpath:mybatis-spring.xml"})
public class SpringMybatisTest {
private static Logger logger = Logger.getLogger(SpringMybatisTest.class);
@Resource
private PlayerService playerService = null;
@Resource
private CoachService coachService;
@Resource
private TeamService teamService;
@Test
public void test1() {
Player player=new Player(24,14);
playerService.insert(player);
// System.out.println(user.getUserName());
// logger.info("值:"+user.getUserName());
logger.info(JSON.toJSONString(player));
}
@Test
public void testGet() {
Player player=playerService.selectById(24);
if(player.getName()!=null&&player.getName().equals("")) {
System.out.println("SUCCESS!");
}
}
@Test
public void testGetInt() {
Player player=playerService.selectById(14);
System.out.println(player.getAge());
}
@Test
public void testOneToOne() {
Coach coach=new Coach("phil jackson", 79, 11111.1f);
coachService.insertCoach(coach);
System.out.println(coach.getId());
Team team=new Team("lakerss", "los angeles", 1112334.1f, coach);
teamService.insertTeam(team);
}
@Test
public void testGetOneToOne() {
Team team=teamService.getTeamById(5);
logger.info(JSON.toJSONString(team));
Coach coach=team.getCoach();
logger.info(JSON.toJSONString(coach));
}
}
package com.wangcc.ssm.dao;
import org.springframework.stereotype.Repository;
import com.wangcc.ssm.entity.Player;
public interface PlayerDao {
// public void updatePlayerById();
public void insertPlayer(Player player);
public Player selectById(Integer id);
}
package com.wangcc.ssm.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.wangcc.ssm.dao.PlayerDao;
import com.wangcc.ssm.entity.Player;
@Service("playerService")
public class PlayerService {
@Autowired
private PlayerDao playerDao;
public void insert(Player player) {
playerDao.insertPlayer(player);
}
public Player selectById(Integer id) {
return playerDao.selectById(id);
}
}