列数不匹配; SQL语句(org.h2.jdbc.JdbcSQLException) - 在使用NamedParameterJdbcTemplate插入表时

时间:2016-02-21 11:00:57

标签: java exception h2 spring-jdbc

我想将User个实例保存到 H2 数据库。

为了将新用户保存到DB,我遇到以下异常:

Caused by: org.h2.jdbc.JdbcSQLException: Column count does not match; SQL statement:
INSERT INTO Users (user_id, user_name, user_birthday, user_email, user_role, user_tickets) 
VALUES (?,     ?,     ?,     ?,     ?,     ) [21002-191]

这是DAO片段:

@Override
public Integer create(User entity) {
    String sql = "INSERT INTO Users (user_id, user_name, user_birthday, user_email, user_role, user_tickets) " +
                                "VALUES (:id,     :name,     :birthday,     :email,     :role,     :tickets)";

    SqlParameterSource parameterSource =
            new MapSqlParameterSource("id", entity.getId())
            .addValue("name", entity.getName())
            .addValue("birthday", entity.getBirthday())
            .addValue("email", entity.getEmail())
            .addValue("role", entity.getRole())
            .addValue("tickets", entity.getBookedTickets());

    Logger.info("Create user: " + entity);
    return getNamedParameterJdbcTemplate().update(sql, parameterSource); <== It fails here 
}

用于创建数据库的SQL脚本如下所示:

----------------------
-- Create Users table
----------------------
CREATE TABLE Users (
  user_id        INTEGER PRIMARY KEY NOT NULL,
  user_name      VARCHAR(30) NULL,
  user_birthday  DATETIME NULL,
  user_email     VARCHAR(30) NULL,
  user_role      VARCHAR(20) NULL,
  user_tickets   VARCHAR(100) NULL,
);

-----------------------
-- Create Tickets table
-----------------------
CREATE TABLE Tickets (
  tick_id        INTEGER PRIMARY KEY NOT NULL,
  event_id       VARCHAR(30),
  tick_price     DECIMAL(8,2),
  user_id        INTEGER,
);

以下是User POJO

public class User {
    private Integer id;
    private String name;
    private Calendar birthday;
    private String email;
    private String role;
    private Set<Ticket> bookedTickets = new HashSet<>();
    // getters / setters

我认为它无法写入Set<Ticket>,但我不知道如何解决此问题。

更新:

为了执行数据库访问,我正在使用 - Spring JDBC。 完全NamedParameterJdbcTemplate

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
<bean class="net.lelyak.edu.dao.NamedParameterJdbcDaoImpl">
    <property name="dataSource" ref="dataSource"/>
</bean>

public class NamedParameterJdbcDaoImpl extends NamedParameterJdbcDaoSupport {

    @Autowired
    private DataSource dataSource;

    @PostConstruct
    private void initialize() {
        setDataSource(dataSource);
    }
}

DAO实施:

@Repository
public class UserDaoImpl extends NamedParameterJdbcDaoImpl implements IGenericDao<User, Integer> {

    @Override
    public Integer create(User entity) {
        // todo move SQL queries to utility class
        String sql = "INSERT INTO Users (user_id, user_name, user_birthday, user_email, user_role, user_tickets) " +
                                    "VALUES (:id,     :name,     :birthday,     :email,     :role,     :tickets)";
    // see create() at above text

有任何建议吗?

3 个答案:

答案 0 :(得分:1)

user_ticketsVARCHAR(100),但您分配给:tickets的值为Set<Ticket>,那该怎么办呢?

Spring并不知道你在做什么,但它假设你在使用多值参数时构建IN子句,例如: x IN (:tickets),因此它会用适当数量的参数标记替换:tickets。例如。如果你的集合有3个值,它将变为x IN (?,?,?)

您的Set为空,因此不会生成任何标记。从技术上讲,我认为它应该抛出异常,因为即使对于IN条款,这也不会有效,但它没有。

那么,如果您的user_tickets有值,那么您期望列Set<Ticket>的价值是多少? Set的字符串版本,例如[Ticket1, Ticket2]?如果是,请拨打toString()

.addValue("tickets", entity.getBookedTickets().toString());

然后交叉你的手指,希望不会超过100个字符。

答案 1 :(得分:1)

抱歉,我忘记了。 我的假设是用户可以有很多票,一张票只属于一个用户。您无法在数据库单元中保存所有集合,因此解决方案是更改关系并在票证上保存用户ID。贝娄就是全部。我创建了服务类,检查你是否有数据库用户,如果没有 - 保存它。

@Entity
public class User {

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private Calendar birthday;
private String email;
private String role;

@Transient
private Set<Ticket> bookedTickets = new HashSet<>(); //I cant save collection into database

//getters and setters

}

@Entity
public class Ticket {

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String desc;
private int number;
/** id of customer as owner this ticket */
@ManyToOne
private User user;

//getters and setters

}

用于保存的userDAO方法中的

public void save(User user){
     String sql = "INSERT INTO User ( name, birthday, email, role) VALUES (:name, :birthday, :email, :role)";

     SqlParameterSource parameterSource =
                new MapSqlParameterSource("name", user.getName())
                .addValue("birthday", user.getBirthday())
                .addValue("email", user.getEmail())
                .addValue("role", user.getRole());

     namedParameterJdbcTemplate.update(sql, parameterSource);

     sql="SELECT id FROM User WHERE name = :name AND birthday=:birthday AND email=:email AND role=:role";
     Integer id = namedParameterJdbcTemplate.query(sql, parameterSource, new ResultSetExtractor<Integer>() {
        @Override
        public Integer extractData(ResultSet result) throws SQLException,DataAccessException {
            return result.getInt("id");
        }
    });
    user.setId(id); 
 }
用于保存的ticketDAO方法中的

     public void save(Ticket ticket){
     String sql = "INSERT INTO Ticket  (desc , number, user_id) VALUES (:desc, :number, :userId)";

     SqlParameterSource parameterSource =
                new MapSqlParameterSource("desc", ticket.getDesc())
                .addValue("number", ticket.getNumber())
                .addValue("userId", ticket.getUser().getId());

     namedParameterJdbcTemplate.update(sql, parameterSource);
 }

和saveTickets的服务:

public class UserService {
private TicketDAO ticketDAO;
private UserDAO userDAO;

public void saveTicketsForUser(User user){
    if(user.getId()==null){
        //if user is not saved in database
        userDAO.save(user);
    }else{
        //if you have this client in database, you don't need to save client
    }
    for(Ticket ticket: user.getBookedTickets()){
        ticket.setUser(user);
        ticketDAO.save(ticket);
    }
}

}

你可以使用xml将dao类注入服务。

答案 2 :(得分:0)

解决方案是重新设计逻辑一点点。将主要部分保存逻辑移动到父抽象类:

public abstract class BaseDAO<ENTITY extends BaseEntity> extends NamedParameterJdbcDaoSupport implements IGenericDao<ENTITY> {

    private final String tableName;
    private final Class<ENTITY> entityClass;
    private final List<String> fields;
    private final String insertSQL;
    private final String updateSQL;

    public BaseDAO(Class<ENTITY> entityClass, String tableName, List<String> fields) {
        this.entityClass = entityClass;
        this.tableName = tableName;
        this.fields = fields;

        // init SQLs
        StringBuilder sbInsertSQL = new StringBuilder();
        StringBuilder sbUpdateSQL = new StringBuilder();

        sbInsertSQL.append("INSERT INTO ").append(tableName).append(" (");
        sbUpdateSQL.append("UPDATE ").append(tableName).append(" SET ");

        for (int i = 0; i < fields.size(); i++) {
            if (i > 0) {
                sbInsertSQL.append(", ");
                sbUpdateSQL.append(", ");
            }
            sbInsertSQL.append(fields.get(i));
            sbUpdateSQL.append(fields.get(i)).append("=:").append(fields.get(i));
        }
        sbInsertSQL.append(") ").append("VALUES (");
        for (int i = 0; i < fields.size(); i++) {
            if (i > 0) {
                sbInsertSQL.append(",");
            }
            sbInsertSQL.append(":").append(fields.get(i));
        }

        sbInsertSQL.append(")\n");
        sbUpdateSQL.append(" WHERE id=:id\n");

        this.insertSQL = sbInsertSQL.toString();
        this.updateSQL = sbUpdateSQL.toString();
        Logger.debug("BaseDAO(), insertSQL: [" + insertSQL + "]");
        Logger.debug("BaseDAO(), updateSQL: [" + updateSQL + "]");
    }

    @Override
    public Long save(ENTITY entity) {
        long res;
        if (entity.getId() == null) {
            res = insert(entity);
        } else {
            update(entity);
            res = entity.getId();
        }
        return res;
    }

Child DAO将如下所示:

public class UserDAO extends BaseDAO<User> {

    private static final String USER_TABLE_NAME = "t_user";
    private static final String userFields[] = {"name", "birthday", "email", "password", "role", "enabled"};

    public UserDAO() {
        super(User.class, USER_TABLE_NAME, Arrays.asList(userFields));
    }   

另外,使用自动递增id的用户表:

----------------------
-- create t_user table
----------------------
CREATE TABLE t_user (
      id INT GENERATED ALWAYS AS IDENTITY CONSTRAINT pk_user PRIMARY KEY,
      name      VARCHAR(60) NOT NULL,
      birthday  DATE,
      email     VARCHAR(60),
      password  VARCHAR(100),
      role      VARCHAR(300),
      enabled   SMALLINT(6)
);

此外,应使用父逻辑更新模型:

public abstract class BaseEntity {

    protected Long id = null;
    protected String name;

public class User extends BaseEntity {
    private Date birthday;
    private String email;
    private String password;
    private String role;
    private boolean enabled;